Einführung CouchDB
-
Upload
oliver-kurowski -
Category
Business
-
view
2.811 -
download
0
description
Transcript of Einführung CouchDB
COUCHDB
<- Achtet auf dieses Auto Oliver Kurowski, @okurow
Wer bin ich Dipl. Inf. (FH) Oliver Kurowski Studium Medieninformatik an der TFH-Berlin (Beuth
Hochschule für Technik) Abschluss 2003 (Dipl.)
Erster Computer: Apple II 1983 Seit 2003: KW automotive GmbH
Div. Fachartikel im PHP-Magazin 2012: Buch „CouchDB mit PHP“
(www.CouchDBmitPHP.de)
Twitter: @okurow Mail: [email protected]
Oliver Kurowski, @okurow
ÜBERBLICK
Oliver Kurowski, @okurow
Was ist CouchDB?Eine NoSQL Datenbank
die Ihre Dokumente in JSON speichert,
JavaScript für MapReduce benutzt,
über HTTP angesprochen wird,
Multi-Master-Replikationen ermöglicht,
mit Attachments an Datendokumenten eigene Programme innerhalb der CouchDB erlaubt,
Und in Erlang geschrieben ist.
2005 Anfang der Entwicklung von Damien Katz, ehemaliger Lotus Notes Entwickler. Seit 2008 hauptberuflich.
Nov 2008: Apache Project
2012: Mit Couchbase ein neues kommerzielles Produkt
Oliver Kurowski, @okurow
Wo läuft CouchDB? Windows (binaries auf couchdb.apache.org) Lunux (Sourcecode auf der Hompage, diverse build-tools
erhältlich) Mac OSX Zum Testen: iriscouch.com / cloudant.com
Oliver Kurowski, @okurow
Übersicht der Funktionen Spricht HTTP Schemalose Dokumente im JSON-Format MVCC Multiversion Concurrency Control MapReduce zum Indizieren und Abfragen der Daten Transformationsfunktionen zum Anpassen der Ausgabe Replikationen mit Filtermöglichkeit Automatische/manuelle Funktionen beim Speichern von
Dokumenten Binäre Attachments für Dokumente Integrierte HTML-Oberfläche Futon
Oliver Kurowski, @okurow
VokabularCouchDB : Eine CouchDB installation
Datenbank: Eine Datenbank innerhalb einer CouchDB
Dokument: Ein Datensatz innerhalb einer Datenbank
Oliver Kurowski, @okurow
Dokumente in CouchDB Schemalos JSON
Keine Gruppierung von Dokumenten, daher Feld “doctype“
Oliver Kurowski, @okurow
{ “_id“: “1“, “_rev“: “1-axcv“, “doctype“: “fahrzeug“, “hersteller“: “Audi“, “modell“: “A3“, “baujahr“: 2000, “extras“: [“Alufelgen“,“Klimaanlage“]}
Kommunikation über HTTPREST API:
GET ruft Daten ab
PUT/POST sendet Daten
DELETE löscht einen Datensatz
COPY kopiert einen Datensatz
Auf der Kommandozeile mit curl:
Oliver Kurowski, @okurow
$ curl –X PUT http://localhost:5984/fahrzeuge/1 -d ‘{“doctype“:“fahrzeug“, “hersteller“:“Audi“, “modell“:“A3“}‘{“ok“:true, “id“:“1“, “rev“:“1-a4b7a34..“}
$ curl –X PUT http://localhost:5984/fahrzeuge {“ok“:true}
$ curl –X GET http://localhost:5984/fahrzeuge/1 {“_id“:“1“, “_rev“: “1-a4b7a34..“, “doctype“: “fahrzeug“, “hersteller“: “Audi“,“modell“: “A3“}
$ curl –X GET http://localhost:5984/fahrzeuge/2{“error“:“not found“, “reason“:“missing“}
MVCC
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlMVCC ermöglicht nichtblockierendes Schreiben auf eine Datenbank.Die neue Version eines Dokuments wird an die alte Version angehangen.Dabei erhöht sich die Revisionsnummer des Dokuments.
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlMVCC ermöglicht nichtblockierendes Schreiben auf eine Datenbank.Die neue Version eines Dokuments wird an die alte Version angehangen.Dabei erhöht sich die Revisionsnummer des Dokuments.
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlMVCC ermöglicht nichtblockierendes Schreiben auf eine Datenbank.Die neue Version eines Dokuments wird an die alte Version angehangen.Dabei erhöht sich die Revisionsnummer des Dokuments.
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlMVCC ermöglicht nichtblockierendes Schreiben auf eine Datenbank.Die neue Version eines Dokuments wird an die alte Version angehangen.Dabei erhöht sich die Revisionsnummer des Dokuments.
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlMVCC ermöglicht nichtblockierendes Schreiben auf eine Datenbank.Die neue Version eines Dokuments wird an die alte Version angehangen.Dabei erhöht sich die Revisionsnummer des Dokuments.
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlBei gleichzeitiger Bearbeitung eines Dokuments „gewinnt“ derjenige, der es zuerst speichert. Dadurch erhöht sich die Revisionsnummer des Dokuments, und der Langsamere hat damit eine überholte Revisionsnummer, das Speichern wird abgelehnt
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlBei gleichzeitiger Bearbeitung eines Dokuments „gewinnt“ derjenige, der es zuerst speichert. Dadurch erhöht sich die Revisionsnummer des Dokuments, und der Langsamere hat damit eine überholte Revisionsnummer, das Speichern wird abgelehnt
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlBei gleichzeitiger Bearbeitung eines Dokuments „gewinnt“ derjenige, der es zuerst speichert. Dadurch erhöht sich die Revisionsnummer des Dokuments, und der Langsamere hat damit eine überholte Revisionsnummer, das Speichern wird abgelehnt
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlBei gleichzeitiger Bearbeitung eines Dokuments „gewinnt“ derjenige, der es zuerst speichert. Dadurch erhöht sich die Revisionsnummer des Dokuments, und der Langsamere hat damit eine überholte Revisionsnummer, das Speichern wird abgelehnt
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlBei gleichzeitiger Bearbeitung eines Dokuments „gewinnt“ derjenige, der es zuerst speichert. Dadurch erhöht sich die Revisionsnummer des Dokuments, und der Langsamere hat damit eine überholte Revisionsnummer, das Speichern wird abgelehnt
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlBei gleichzeitiger Bearbeitung eines Dokuments „gewinnt“ derjenige, der es zuerst speichert. Dadurch erhöht sich die Revisionsnummer des Dokuments, und der Langsamere hat damit eine überholte Revisionsnummer, das Speichern wird abgelehnt
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlBei gleichzeitiger Bearbeitung eines Dokuments „gewinnt“ derjenige, der es zuerst speichert. Dadurch erhöht sich die Revisionsnummer des Dokuments, und der Langsamere hat damit eine überholte Revisionsnummer, das Speichern wird abgelehnt
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlBei gleichzeitiger Bearbeitung eines Dokuments „gewinnt“ derjenige, der es zuerst speichert. Dadurch erhöht sich die Revisionsnummer des Dokuments, und der Langsamere hat damit eine überholte Revisionsnummer, das Speichern wird abgelehnt
Oliver Kurowski, @okurow
MVCC – Multiversion Concurrency ControlDie Revisionsnummer eines Dokuments wird von der CouchDB gesetzt.
Jede Änderung eines Dokuments muss mit der aktuellen Revisionsnummer angegeben werden.
Oliver Kurowski, @okurow
$ curl –X PUT http://localhost:5984/fahrzeuge/1 -d ‘{“doctype“:“fahrzeug“, “hersteller“:“Audi“, “modell“:“A3“, “baujahr“:2000, “_rev“:“1-a4b7a34..“}‘{“ok“:true, “id“:“1“, “rev“:“2-7c45a..“}
$ curl –X DELETE http://localhost:5984/fahrzeuge/1?rev=2-7c45a..{“ok“:true, “id“:“1“, “rev“:“3-e94x..“}
$ curl –X PUT http://localhost:5984/fahrzeuge/1 -d ‘{“doctype“:“fahrzeug“, “hersteller“:“Audi“, “modell“:“A3“, “baujahr“:2000“}‘{“error“:“conflict“, “reason“:“Document update conflict“}
$ curl –X GET http://localhost:5984/fahrzeuge/1{“error“:“not found“, “reason“:“deleted“}
ABFRAGEN
Oliver Kurowski, @okurow
ViewsEine View ist ein Container für eine Map- und optional Reduce Funktion.
Standardmäßig sind die Funktionen in JavaScript geschrieben,andere Sprachen können aber eingestellt werden.
Views können Daten Filtern Gruppieren Auswerten
Oliver Kurowski, @okurow
Views Views werden innerhalb spezieller Dokumente (Designdokumente) in einer Datenbank gespeichert, und wie Datendokemente abgerufen:
Bsp: http://localhost:5984/fahrzeugDatenbank/_design/abfragen/_view/nachHersteller
Oliver Kurowski, @okurow
{ “_id“: “_design/abfragen“, “_rev“: “1-e7fg“, “views“: { “nachHersteller“: { “map“:“function(doc) {…}“, “reduce“:“function(keys,values) {…}“ } }}
Die Map-FunktionHat ein einzelnes Dokument als Input
Kann Key/Value Paare ausgeben. Dabei können die Werte aus allen JSON-Typen bestehen:
- Spezialwerte: null, true, false- Zahlen: 1e-17, 1.5, 200- Strings : “+“, “1“, “Ab“, “Audi“
- Arrays: [1], [1,2], [1,“Audi“,true]
- Objekte: {“price“:1300,“sold“:true}
Die Ausgegebenen Key/Value Paare aller Dokumente werden in einer Liste gespeichert,die nach den Werten der Keys aufsteigend sortiert ist.
Zusätzlich zu jedem Key/Value paar wird auch die Dokument-ID ausgegeben
Oliver Kurowski, @okurow
Die Map-Funktion Beispiel
Oliver Kurowski, @okurow
Datendokumente:
Map-Funktion in JavaScript in einer View “nachPreis“:
Ergebnis von _view/nachPreis:
Id: 2hersteller : Audimodell: A4baujahr : 2009preis: 16.000
Id: 1hersteller: Audimodell: A3baujahr: 2000preis: 5.400
Id: 3hersteller : VWmodell: Golfbaujahr : 2009preis: 15.000
Id: 4hersteller : VWmodell: Golfbaujahr : 2008preis: 9.000
Id: 5hersteller : VWmodell: Polobaujahr : 2010preis: 12.000
„nachPreis“: { “map“:“function(doc) { emit (doc.preis, doc.hersteller+‘-‘+doc.modell+‘-‘+doc.baujahr); }“}
{"total_rows":5,"offset":0,"rows":[ {"id":"1","key":5.400,"value":“Audi-A3-2000“}, {"id":"2","key":9.000,"value":“VW-Golf-2008“}, {"id":"3","key":12.000,"value":“VW-Polo-2010“}, {"id":"4","key":15.000,"value":“VW-Golf-2009“}, {"id":"5","key":16.000,"value":“Audi-A4-2009“} ]}
Die Map-Funktion steuern
Oliver Kurowski, @okurow
Zur Abfragezeit kann die Ausgabe eine View beeinflusst werden:
_view/nachPreis?key=9.000
_view/nachPreis?endkey=10.000
_view/nachPreis?startkey=10.000&endkey=15.500
{"total_rows":5,"offset":1,"rows":[ {"id":"2","key":9.000,"value":“VW-Golf-2008“},]}
{"total_rows":5,"offset":0,"rows":[ {"id":"1","key":5.400,"value":“Audi-A3-2000“}, {"id":"2","key":9.000,"value":“VW-Golf-2008“},]}
{"total_rows":5,"offset":2,"rows":[ {"id":"3","key":12.000,"value":“VW-Polo-2010“}, {"id":"4","key":15.000,"value":“VW-Golf-2009“} ]}
Die Reduce-Funktion Hat die Ergebnisse aus der Map-Phase als Input Soll diese Liste auf einen einzelnen Wert reduzieren Wird nach der Map-Phase automatisch ausgeführt
Oliver Kurowski, @okurow
Die Reduce-Funktion BeispielErweiterung der View „nachPreis“
Ergebnis von _view/nachPreis:
_view/nachPreis?endkey=10.000
Oliver Kurowski, @okurow
„nachPreis“: { “map“:“function(doc) { emit (doc.preis, doc.hersteller+‘-‘+doc.modell+‘-‘+doc.baujahr); }“, “reduce“:“function(keys,values) { return (count(values) ); //_count }“ }}
{"rows":[ {"key":null, "value":5 } ]}
{"rows":[ {"key":null, "value":2 } ]}
View Parameter limit: Begrenzt die Anzahl der Ergebnisse skip: Überspringt eine Anzahl von Egebnissen include_docs=true: Ohne Reduce-Phase wird das
entsprechende Dokument zusätzlich zu jeder Zeile der Map-Liste ausgegeben
key, startkey,endkey: Bestimme Keys, bzw. Bereiche liefern startkey_docid=x: Nur Dokumente mit der id>=x endkey_docid=x: Nur Dokumente mit der id<x descending=true: Umgekehrte Sortierreihenfolge (!) stale=ok: Zeige die gespeicherten Werte, kein Aufruf der View stale=update_after: Zeige die gespeicherten Werte, danach
Aufruf der View und Durchlauf MapReduce Phase group, group_level,reduce=false: Gruppierungen der Keys,
sofern Arrays
Oliver Kurowski, @okurow
Oliver Kurowski, @okurow
DEMO MAPREDUCE IN FUTON
TRANSFORMATION
Oliver Kurowski, @okurow
Transformationsfunktionen Dokumente sind JSON Objekte
Ergebnisse von Views sind ebenfalls JSON Objekte
CouchDB kann Serverseitig diese Daten formatiert liefern.
Shows: Transformationen für Datendokumente
Lists: Transformationen für Views
Shows und Lists werden zur Abfragezeit verarbeitet, d.h. bei großen Datenmengen einen erheblichen Zeitaufwand.
Oliver Kurowski, @okurow
Shows: Dokumente transformieren
Oliver Kurowski, @okurow
{“Id“: “1“,“hersteller“: “Audi“,“modell“: “A3“,“baujahr“: 2000,“preis“: 5.400}
Datendokument (JSON): Show-Funktion in dem Designdokument:
Ergebnis von _show/inHTML/1 :
“shows“: { “inHTML“:“function(doc,req) { var outS=‘‘; outS=‘<b>‘+doc.hersteller+‘ ‘+doc.modell+‘</b><br>`; outS+=‘Baujahr: ‘+doc.baujahr+‘<br>‘; outS+=‘Preis: ‘+doc.preis; return outS; }“ }
Audi A3Baujahr: 2000Preis: 5.400
List: Views transformierenErgebnis einer View nachPreis: List-Funktion in dem Designdokument:
Ergebnis von _list/alsLi/nachPreis :
Alle Steuerfunktionen einer View sind auch hier benutzbar
Oliver Kurowski, @okurow
“lists“: { “alsLI “:“function(head,req) { start({'code':200,'headers':{'Content-Type':'text/html'}}); while(row=getRow()) { send(‘<li>‘+row.value+‘ :‘ +row.key+‘</li>‘); } }“}
• Audi-A3-2000 : 5.400• VW-Golf-2008 : 9.000• VW-Polo-2010 :
12.000• VW-Golf-2009 :
15.000• Audi-A4-2009 :
16.000
{"total_rows":5,"offset":0,"rows":[ {"id":"1","key":5.400,"value":“Audi-A3-2000“}, {"id":"2","key":9.000,"value":“VW-Golf-2008“}, {"id":"3","key":12.000,"value":“VW-Polo-2010“}, {"id":"4","key":15.000,"value":“VW-Golf-2009“}, {"id":"5","key":16.000,"value":“Audi-A4-2009“} ]}
Das Request ObjektShows und Lists haben Zugriff auf das Request Objekt mit folgenden Feldern:
(die Interessantesten)
Info: Informationen über die DatenbankId: übergebene Dokument ID (null, wenn keine übergeben wurde)requested_path: Der Aufruf in Einzelteilen, wie er gesendet wurdeheaders: Vom Client gesendeter HeaderuserCTX: Informationen über den angemeldeten Benutzerquery: angehangene Parameter
Das Feld req.query erlaubt das Hinzufügen eigener Parameter an eine Abfrage zum Steuern der Show/List.Bsp: _show/inHTML/1?zeigePreis=true
Abfrage In der show-Funktion:Oliver Kurowski, @okurow
if(req.query.zeigePreis==true) { ….}
SPEICHERFUNKTIONEN
Oliver Kurowski, @okurow
Funktionen beim Speichernvalidate_doc_update Automatische Validierung beim Speichern, kann Speichern
verhindern Funktionen werden automatisch aufgerufen Pro Designdokument nur eine Funktion Beim Speichern werden die Funktionen aller Designdokumente
einer Datenbank durchlaufen
Oliver Kurowski, @okurow
Funktionen beim Speichernvalidate_doc_update Eingabgsparameter: Das neues Dokument eventuell vorhandenes Dokument mit der angegebenen ID UserContext
Oliver Kurowski, @okurow
function (newDoc,oldDoc,userCTX) { if(!newDoc.name || newDoc.name==‘‘) { throw ({ forbidden:‘Kein Name angegeben‘}); }}
function (newDoc,oldDoc,userCTX) { if(newDoc.name != oldDoc.name) { throw ({ forbidden:‘Name darf nicht verändert werden‘}); }}
function (newDoc,oldDoc,userCTX) { if(userCTX.name !=‘oliver‘ ) { throw ({ forbidden:‘Nur User oliver darf Daten schreiben‘}); }}
Funktionen beim Speichernupdate Datendokumente vor dem Speichern verändern, bzw.
Speichern verhindern expliziter Aufruf pro Speichervorgang Mehrere Funktionen in einem Designdokument Aber nur eine Funktion pro Speichervorgang auswählbar
Oliver Kurowski, @okurow
Funktionen beim Speichernupdate Eingabgsparameter: eventuell vorhandenes Dokument mit der angegebenen ID request (mit neuen Daten im body-Feld)
Ermöglicht einfache Operationen auf Datendokumente, ohne die Dokumente vorher zu laden:
Datendokument: update-Funktion:
Aufruf:
Oliver Kurowski, @okurow
function (doc,req) { if(doc!=null) { doc.stimmen+=1; return ({doc, ‘Anzahl der Stimmen:‘+doc.stimmen]); }else{ return ({null,‘Person ‘+req.id+‘ nicht gefunden‘})}
{ “_id“:“oliver“, “stimmen“:0}
$ curl –X PUT http://localhost:5984/personen/_design/operationen/update/pluseins/oliverAnzahl Stimmen: 1
$ curl –X PUT http://localhost:5984/personen/_design/operationen/update/pluseins/oliPerson oli nicht gefunden
ATTACHMENTS
Oliver Kurowski, @okurow
AttachmentsAn bestehende Dokumente können Binärdaten angehangen werden
Große Dateien (Video, MP3) können ausschnittsweise geladen werden
Abrufen der Datei über die URL des Datendokuments:
http://localhost:5984/fahrzeuge/1/bild.jpg
http://localhost:5984/homepage/de/index.html
Das „/“ kann ein Teil des Dokumentnamens sein, dadurch Baumstruktur möglichhttp://localhost:5984/homepage/de/bilder/1.jpg
Oliver Kurowski, @okurow
AttachmentsErzeugen einer neuen Datenbank und leeren Datensatzes:
Lokale Datei intex.html als Attachment speichern
Aufruf im Browser:
Oliver Kurowski, @okurow
$ curl -X PUT http://localhost:5984/websites{“ok“:true}
$ curl -X PUT http://localhost:5984/websites/de -d‘{}‘{"ok":true,"id":"de","rev":"1-967a00dff5e02add41819138abb3284d"}
$ curl -X PUT http://localhost:5984/websites/de/index.html?rev=1-967a00dff5e02add41819138abb3284d -d @index.html -H “Content-type:text/html“{"ok":true,"id":"de","rev":"2-f3f26739438d8f95570669c8fc886164"}
http://localhost:5984/websites/de/index.html
REPLIKATION
Oliver Kurowski, @okurow
Replikation
Oliver Kurowski, @okurow
Master-Client
Datenbank a Datenbank b
Replikation
Oliver Kurowski, @okurow
Datenbank a Datenbank b
Master-Master
Replikation
Oliver Kurowski, @okurow
Datenbank a Datenbank b
Datenbank c
Master-Master-Client
Replikation
Oliver Kurowski, @okurow
Datenbank a Datenbank b
Datenbank cDatenbank e
Datenbank f
Datenbank g
Multi-Master
Replikation
Oliver Kurowski, @okurow
Daten werden immer nur in eine Richtung Abgeglichenpull: Datenbank holt sich die Datenpush: Datenbank schickt Daten
Bei Master-Master: 2 gegenseitige Replikationen
Es werden nur Änderungen von Dokumenten verglichen (_changes Feed), d.h. wenn ein Dokument auf dem Client vorhanden ist, und auf dem Master nicht, wird das Client-Dokument bei der Replikation nicht gelöscht.
Verglichen werden nur die Revisionsnummern. Höhere Revisionsnummer des Dokuments beim Master = Client wird aktualisiert.
Ein gelöschtes Dokument bleibt weiterhin in der Datenbank. Löschen ist eine Änderung an einem Dokument (_deleted:true), und gelöschte Dokumente werden repliziert.
Replikation
Oliver Kurowski, @okurow
Start einer Replikation über POST Befehl an _replicate
$ curl -X POST http://localhost:5984/_replicate -H "Content-type:application/json" -d '{"source":"datenbank_a","target":"datenbank_b"}'{"ok":true,"session_id":"b4ecde6f3b4a3948e51a409894b18a5c","source_last_seq":1,"replication_id_version":2,"history":[{"session_id":"b4ecde6f3b4a3948e51a409894b18a5c","start_time":„Sun, 10 Jun 2012 09:55:16 GMT","end_time":„Sun, 10 Jun 2012 09:55:16 GMT","start_last_seq":0,"end_last_seq":1,"recorded_seq":1,"missing_checked":1,"missing_found":1,"docs_read":1,"docs_written":1,"doc_write_failures":0}]}
Replikation
Oliver Kurowski, @okurow
Oder durch Eintrag in die _replicator Datenbank
Dieses Dokument nach der Replikation:
$ curl -X PUT http://localhost:5984/_replicator/einmalig -d {"source":"datenbank_a","target":"datenbank_b"}' {"ok":true,"id":"einmalig","rev":"1-57c8e9e3949e9fa48c135f8cdae3d88e"}
{ "_id": "einmalig", "_rev": "3-4700e5caef6496219e032322427bca43", "source": "datenbank_a", "target": "datenbank_b", "owner": null, "_replication_state": "completed", "_replication_state_time": "2012-06-10T03:05:35-07:00", "_replication_id": "44534eb9bb71658fe1698fc69d62f88b", "_replication_stats": { "revisions_checked": 2, "missing_revisions_found": 1, "docs_read": 1, "docs_written": 1, "doc_write_failures": 0, "checkpointed_source_seq": 3 } }
Replikation
Oliver Kurowski, @okurow
Besonderheiten bei Replikationen:
Continuous Replication: Replikationen müssen nicht einzeln angestoßen werden, Änderungen werden kontinuierlich weitergegeben
Named Document Replication:Nur einzeln benannte Dokumente werden repliziert
Auch Designdokumente können repliziert werden. Bei Verwendung von Applikation in der CouchDB einfaches Deployment der App über viele Datenbanken automatisch möglich.
Replikation
Oliver Kurowski, @okurow
Ein Highlight:
Filtered Replication: Eine Funktion erlaubt/ verhindert die Replikation von Dokumenten{
“_id“:“_design/filterfunktionen“, “filters“: { “ab18“:“function(doc,req) { if(doc.alter && doc.alter>17) { return true; } else { return false; } }“ }}
$ curl -X PUT http://localhost:5984/_replicator/einmalig -d {"source":"datenbank_a","target":"datenbank_b“, “filter”:”filterfunktionen/ab18”}'
Oliver Kurowski, @okurow
LINKS (RECHTS)
http://CouchDBmitPHP.de
http://apache.couchdb.org
http://berlin.couchdb.org
Irc: #couchdb