CouchDB – tak trochu jiná databáze (2. část)

CouchDB je dokumentově orientovaná databáze s HTTP RESTful rozhraním. Před týdnem jsme si představili základní koncepty, ze kterých vychází, a stručně si vysvětlili, jak ji nainstalovat a jak ji ovládat. Dnes si přečtěte pokračování, v němž si řekneme něco víc o správě databází a dokumentů.

Seriál: Nerelační databáze (11 dílů)

  1. CouchDB – tak trochu jiná databáze (1. část) 24.8.2009
  2. CouchDB – tak trochu jiná databáze (2. část) 31.8.2009
  3. CouchDB – tak trochu jiná databáze (3. část) 7.9.2009
  4. MySQL v roli neschémové databáze 6.1.2010
  5. Základy Amazon SimpleDB 30.3.2010
  6. Návrh databáze – NoSQL vs SQL 31.3.2010
  7. Amazon SimpleDB prakticky v PHP 15.4.2010
  8. Vyskúšajme si Tokyo Cabinet 4.5.2010
  9. Redis: key-value databáze v paměti i na disku 7.10.2010
  10. Přechod z MySQL na CouchDB, část první 17.2.2011
  11. Přechod z MySQL na CouchDB: Druhý díl 24.2.2011

předchozím dílu jsme si představili nerelační databázi CouchDB, vysvětlili jsme si některé pojmy a ukázali, jak ji lze nainstalovat pro jednotlivá prostředí.

Databáze

První řádek je příkaz shellu, který curl říká, že chceme, aby se co nejvíce „vykecávala“ a aby poslala GET požadavek na adresu http://localhost:5984/_all_dbs (klidně si ji otevřete v prohlížeči):

$ curl -vX GET http://localhost:5984/_all_dbs
* About to connect() to localhost port 5984 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 5984 (#0)
> GET /_all_dbs HTTP/1.1
> User-Agent: curl/7.19.5 (i686-pc-linux-gnu) libcurl/7.19.5 OpenSSL/0.9.8k zlib/1.2.3.3
> Host: localhost:5984
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: CouchDB/0.10.0a799862 (Erlang OTP/R13B)
< Date: Mon, 10 Aug 2009 07:02:56 GMT
< Content-Type: text/plain;charset=utf-8
< Content-Length: 3
< Cache-Control: must-revalidate
<
[]
* Connection #0 to host localhost left intact
* Closing connection #0

Spojíme se s localhostem na portu 5984:

* About to connect() to localhost port 5984 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 5984 (#0)

Odešleme GET požadavek na /_all_dbs (tělo je prázdné):

> GET /_all_dbs HTTP/1.1
> User-Agent: curl/7.19.5 (i686-pc-linux-gnu) libcurl/7.19.5 OpenSSL/0.9.8k zlib/1.2.3.3
> Host: localhost:5984
> Accept: */*
>

Dostaneme odpověď:

< HTTP/1.1 200 OK
< Server: CouchDB/0.10.0a799862 (Erlang OTP/R13B)
< Date: Mon, 10 Aug 2009 07:02:56 GMT
< Content-Type: text/plain;charset=utf-8
< Content-Length: 3
< Cache-Control: must-revalidate
<
[]

Poslední řádek – []  – je tělo odpovědi. Je to výraz v JSON a znamená prázdné pole – takže tu není žádná databáze (je možné, že v některé verzi CouchDB přijde s ukázkovou databází vytvořenou při instalaci serveru, takže se vám ukáže spíše něco jako  ["skvela_ukazkova_databaze"]):

A spojení ukončíme:

* Connection #0 to host localhost left intact
* Closing connection #0

Aby seznam databází nebyl takový prázdný, vytvořme si novou databázi (zájemcům radím opět přidat přepínač  -v):

$ curl -X PUT http://localhost:5984/test/
{"ok":true}

{"ok":true} nám říká, že všechno proběhlo v pořádku. Pokud CouchDB chce sdělit, že se nevyskytly problémy (a nemusí se jednat jen o vytváření databáze), dostanete podobnou JSON odpověď. Znak { a } oznamuje začátek, resp. konec, JSONovského objektu (mapy, hashmapy, slovníku, asociativního pole, či jak tomu říkáte vy) a "ok":true znamená, že na klíči ok se nachází hodnota logické jedničky, pravdy, true. Při jiných odpovědích může CouchDB využít kromě klíče ok ještě další, čímž vám předá dodatečné informace.

Zkuste si schválně příkaz spustit znovu:

$ curl -X PUT http://localhost:5984/test/
{"error":"file_exists","reason":"The database could not be created, the file already exists."}

Vrácením {"error":"...","reason":"..."} (krom jiného; přidejte přepínač -v a sledujete návratový kód HTTP odpovědi) zase CouchDB dává vědět o chybách.

Když se teď poptáme, co že to za databáze všechno máme:

$ curl -X GET http://localhost:5984/_all_dbs
["test"]

Očekávaný výsledek – nebyla zde žádná databáze, jednu jsme přidali a je tu jedna.

Nějaké informace o databázi se dají získat pomocí (místo těch tří teček budou následovat další informace):

$ curl -X GET http://localhost:5984/test/
{"db_name":"test","doc_count":0,"doc_del_count":0,...}

Teď se databáze zbavíme:

$ curl -X DELETE http://localhost:5984/test/
{"ok":true}

Všechno proběhlo v pořádku – GET na /_all_dbs opět bude vracet, co na začátku.

Poslední poznámka ke jménu databáze: jméno musí sestávat pouze z malých písmen anglické abecedy, čísel, nebo znaků _ $ ( ) + - / (musí se escapovat – tzn. že místo / bude %2F apod.), přičemž na prvním místě musí být písmeno.

Dokument

CouchDB by se jen těžko mohla nazývat dokumentově orientovanou databází, kdyby nepracovala s dokumenty. Jak již bylo řečeno, každý dokument je JSONovský objekt, který má přiřazen jedinečný identifikátor. Na ID dokumentu nejsou tak přísné zásady jako na název databáze, ale opět musíte znaky jako / escapovat.

Vytvořme si nový dokument. Jako příklad se typicky používá databáze CD, takže se toho držme. Nechť soubor gorillaz.json obsahuje:

{
    "title" : "Gorillaz",
    "artist" : "Gorillaz",
    "year" : 2001
}

Toto je JSONovský objekt, který nám popisuje jedno album. Aby se z něj stal dokument v databázi, musíme ho tam uložit. Vytvoříme si tedy databázi  alba:

$ curl -X PUT http://localhost:5984/alba/
{"ok":true}

A vložíme do ní jedno:

$ curl -X PUT -d@gorillaz.json http://localhost:5984/alba/6b2ba2478bc39b27fff7d72347880dfd
{"ok":true,"id":"6b2ba2478bc39b27fff7d72347880dfd","rev":"1-644ebaa027af82a5e8f2f81e50a8f084"}

-d@gorillaz.json říká curl, aby poslala v těle požadavku obsah souboru gorillaz.json. ID dokumentu je řetězec 6b2ba2478bc39b27fff7d72347880dfd. Pozorování: URI dokumentu má tvar /<databáze>/<id dokumentu>. Jelikož CouchDB na klíči ok vrací true všechno proběhlo v pořádku. Dále potvrzuje, že ID dokumentu je opravdu takové, jaké je v adrese. V rev  je revize dokumentu.

Kdyby se někdo ptal, jak jsem přišel právě k ID 6b2ba2478bc39b27fff7d72347880dfd, tak to jsem si nechal vygenerovat CouchDB. Pokud pošlete GET požadavek na /_uuids, CouchDB vám může vrátit třeba právě tohle UUID:

$ curl -X GET http://localhost:5984/_uuids
{"uuids":["6b2ba2478bc39b27fff7d72347880dfd"]}

Query parametrem count můžete dále upřesnit, kolik záznamů má CouchDB vrátit.

To jako ID musí být takový obludný řetězec? Ale vůbec nemusí. Klidně by se třeba dokument s albem Gorillaz mohl jmenovat Gorillaz a ničemu by to nemělo vadit.

Pokud je vám jedno, jaké ID bude dokument mít, pošlete POST požadavek na adresu databáze, do které ho chcete umístit:

$ curl -X POST -d@gorillaz.json http://localhost:5984/alba/
{"ok":true,"id":"ae72034c1a3a905dbe82b4b1fd5849fc","rev":"1-644ebaa027af82a5e8f2f81e50a8f084"}

Pro získávání věcí je GET požadavek – a ani u dokumentů tomu není jinak:

$ curl -X GET http://localhost:5984/alba/6b2ba2478bc39b27fff7d72347880dfd
{"_id":"6b2ba2478bc39b27fff7d72347880dfd","_rev":"1-644ebaa027af82a5e8f2f81e50a8f084","title":"Gorillaz","artist":"Gorillaz","year":2001}

Dostaneme JSON objekt, který jsme uložili (formátovaní je jiné – byly odstraněny přebytečné bílé znaky) a opět ID a revizi dokumentu, tentokrát prefixované podtržítkem. Pozorování: Většina „speciálních“ věcí v CouchDB začíná podtržítkem.

Co ta databáze s těmi revizemi pořád má? Nebýt revizí, dokumenty bychom mohli akorát vytvářet a získávat – nemohli bychom je měnit ani mazat. Kdyby nebylo revizí, CouchDB by nevěděla, jestli náhodou mezitím, co klient dokument měl u sebe, mohl ho měnit atp., někdo jiný neuložil novou revizi dokumentu, či dokument nesmazal. Proto nám řekne tohle je ten a ten dokument v té a té revizi. A když budeme chtít uložit změny a naše revize nebudou souhlasit s nejaktuálnější revizí v databázi, CouchDB nám řekne, že takový pokus je konfliktní s již uloženým dokumentem.

Takže druhý pokus o přidání dokumentu skončí nějak takto:

$ curl -X PUT -d@gorillaz.json http://localhost:5984/alba/6b2ba2478bc39b27fff7d72347880dfd
{"error":"conflict","reason":"Document update conflict."}

Ale pokud soubor gorillaz.json pozměníme na (podívejte se na revizi vrácenou v posledním GET požadavku):

{
    "_rev" : "1-644ebaa027af82a5e8f2f81e50a8f084",
    "title" : "Gorillaz",
    "artist" : "Gorillaz",
    "year" : 2001
}

A opět zkusíme PUT:

$ curl -X PUT -d@gorillaz.json http://localhost:5984/alba/6b2ba2478bc39b27fff7d72347880dfd
{"ok":true,"id":"6b2ba2478bc39b27fff7d72347880dfd","rev":"2-a9193383bbc1e2104733c64f27e1e757"}

Úspěch se dostavil a byla vytvořena nová revize dokumentu. I když takovéhle aktualizace, kdy se vlastně nic nemění, jsou k ničemu.

Stejně jako reálné dokumenty mají přílohy, tak je mají i CouchDB dokumenty. Můžete je posílat v těle požadavku v JSONovském objektu spolu s ostatními daty. Řekněme, že chceme k albu přidat ještě jeho cover. Pak by soubor gorillaz.json mohl vypadat následovně (data obrázku samozřejmě nejsou celá, článek by sice nabral hezkých pár kilobytů, ale k čemu to…):

{
    "_attachments" : {
        "cover.jpg" : {
            "content_type" : "image/jpeg",
            "data" : "/9j/4AAQSkZJRgABAQEAnQCdA..."
        }
    },
    "title" : "Gorillaz",
    "artist" : "Gorillaz",
    "year" : 2001
}

Dokument je JSONovský objekt a pro přílohy je vyhrazeno místo pod klíčem _attachments. V data  každé přílohy je samotný soubor zakódovaný v BASE64.

Máte-li CouchDB verze 0.9 a vyšší, můžete k dokumentu přidat přílohu pomocí dalšího požadavku. Řekněme, že příloha cover.jpg je opravdu v souboru  cover.jpg):

$ curl -X PUT -d@cover.jpg -H "Content-Type: image/jpeg" 
> http://localhost:5984/alba/6b2ba2478bc39b27fff7d72347880dfd/cover.jpg?rev=2-a9193383bbc1e2104733c64f27e1e757
{"ok":true,"id":"6b2ba2478bc39b27fff7d72347880dfd","rev":"3-a7a349ee2ffd90145a3c7e8063ef619e"}

Přepínačem -H přidáme/změníme hlavičku požadavku. Nám jde o Content-Type, protože s takovým, jaký se nastaví při nahrávání, ho bude CouchDB i servírovat zpátky. Zkuste si otevřít http://localhost:5984/…fd/cover.jpg a měli byste vidět nahraný obrázek.

Když teď otevřete http://localhost:5984/…d72347880dfd, uvidíte, že přibylo další pole – _attachments  –, které bude obsahovat něco podobného jako:

{"cover.jpg":{"stub":true,"content_type":"image/jpeg","length":9070,"revpos":3}}

"stubs":true říká, že tohle není celá příloha a že bychom si o ni měli zažádat sólo. Pokud chcete, aby CouchDB přibalila i přílohy, přijdete query parametr attachments=true  – např. tedy http://localhost:5984/…d72347880dfd?….

Zbývá nám už jen, jak takový dokument vymazat. Kupodivu k tomu slouží HTTP sloveso  DELETE:

$ curl -X DELETE http://localhost:5984/alba/6b2ba2478bc39b27fff7d72347880dfd?rev=3-a7a349ee2ffd90145a3c7e8063ef619e
{"ok":true,"id":"6b2ba2478bc39b27fff7d72347880dfd","rev":"4-1cadcb1824f385885980fadbdcb138dc"}

Pozorování: vymazání je vlastně vytvoření nové revize. A pokud se teď zeptáte CouchDB na dokument, tak vám opravdu odpoví, že byl vymazán:

$ curl -X GET http://localhost:5984/alba/6b2ba2478bc39b27fff7d72347880dfd
{"error":"not_found","reason":"deleted"}

Když vidíte, jak pracují revize, mohlo by vás napadnout, že by se z CouchDB dal udělat verzovací systém. Není tomu ale tak. Revize opravdu řeší jen problém přístupu více klientů naráz. A je hned několik způsobů, jak o předchozí revize přijít.

Ve třetí a poslední části se těště na replikaci, stlačování databázových souborů a pohledy.

Autor programuje v Javascriptu, PHP, Javě, Golangu… ve všem možném. Ve volném čase probádává nejrůznější zákoutí světa programovacích jazyků a databází a všeho kolem nich.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 5

Přehled komentářů

kokon hmmmm
ornyx Využití
Jakub Kulhan Re: Využití
Michal Augustýn Re: CouchDB – tak trochu jiná databáze (2. část)
Jakub Kulhan Re: CouchDB – tak trochu jiná databáze (2. část)
Zdroj: https://www.zdrojak.cz/?p=3070