Решил на досуге в рабочее время поковырять CouchDB, если NoSQL вам в новинку, то эта статейка для вас окажется полезной.
Установку CouchDB описывать не буду, собственно его сборкой я и не занимался. Перейдем сразу к Futon’у
Futon
После сборки и запуска CouchDB по адресу http://localhost:5984/_utils/ вам будет доступен web-интерфейс для управления БД — это и есть Futon. При помощи данного инструмента мы и будет проводить все описанные далее манипуляции.
Создание базы данных
Тут вроде все просто, кликаем Create Database:
Наблюдал странный баг, когда попытался создать БД “example” и “example/users”, Futon ругался, я не понял… Возможно он плохо дружит с БД в названии которых присутствует слэш.
Создание документов
Теперь пора создавать документы по которым будем осуществлять дальнейший поиск. Возьмем тривиальную задачу — будем «забивать» библиотеку:
Id документа должен быть строкой, по этой причине Id=1 заключен в кавычки, конечно можно использовать и автоматически генерируемое значение, и это даже правильно (иначе вам придется следить за ними на уровне приложения), но уж больно они громоздкие, по этой причине я буду сочинять свои.
Немного погодя понимаешь, что необходимо сохранять не просто текстовое поле, а набор данных, нам понадобится массив или даже объект (после и перед скобочками необходимо ставить пробел или перенос каретки):
Теперь можем чуть-чуть потренироваться опрашивая сервер:
Информация о БД — http://localhost:5984/simple/:
{ "db_name":"simple", "doc_count":4, "doc_del_count":1, "update_seq":9, "purge_seq":0, "compact_running":false, "disk_size":36953, "instance_start_time":"1284023758033358", "disk_format_version":5, "committed_update_seq":9 }
Вывод записи по Id — http://localhost:5984/simple/1/:
{ "_id":"1", "_rev":"3-eef52c024c2b27dadda90e0a510015b8", "title":"Улитка на склоне", "ISBN":["978-5-17-057930-3","978-5-403-00677-4"] }
Вывод всех записей — http://localhost:5984/simple/_all_docs:
{"total_rows":4,"offset":0,"rows":[ {"id":"1","key":"1","value":{"rev":"3-eef52c024c2b27dadda90e0a510015b8"}}, {"id":"2","key":"2","value":{"rev":"2-ccf196a429e2f118a7b558eb049e5773"}}, {"id":"3","key":"3","value":{"rev":"1-d49358e63c151f2f3ea7066146f24a66"}}, {"id":"44","key":"44","value":{"rev":"1-c0d28980139f7f4c372ca86306147b55"}} ]}
Так же есть несколько дополнительных опции, но на них остановимся чуть позже.
REST API
Но не Fucon’ом единым, CouchDB предоставляет REST интерфейс для работы с базой (собственно, Fucon через него и работает, можете посмотреть в консоли FireBug’a — очень наглядно), а это значит приблизительно следующее:
- Если мы хотим получить информацию — отправляем GET запрос (как в примерах выше)
- Если надо создать документ — POST
- Необходимо что-то изменить — PUT
- COPY для копирования
- DELETE для удаления
Что-бы не быть голословным приведу пример создания и изменения документа:
# создаем документ, UUID генерируется автоматически curl -X POST http://localhost:5984/simple -d '{"title":"Hello World!!!"}' -H "Content-Type:application/json" >> {"ok":true,"id":"dbc21298bfb3f78ebe815cdb7000ae98","rev":"1-f33e3c9b23e594593e93181763f0fb1b"} # создаем документ, UUID задаем явно curl -X PUT http://localhost:5984/simple/hello -d '{"title":"Hello World!"}' -H "Content-Type:application/json" >> {"ok":true,"id":"hello","rev":"1-f33e3c9b23e594593e93181763f0fb1b"} # получение документа curl -X GET http://localhost:5984/simple/hello >> {"_id":"hello","_rev":"1-f33e3c9b23e594593e93181763f0fb1b","title":"Hello World!!!"} # редактирование документа, необходимо указывать ревизию редактируемого документа curl -X PUT http://localhost:5984/simple/hello -d '{"_rev":"1-f33e3c9b23e594593e93181763f0fb1b","body":"My world"}' -H "Content-Type:application/json" >> {"ok":true,"id":"hello","rev":"2-493961fb199ea564f532de44d8be2650"} # копирование документа curl -X COPY http://localhost:5984/simple/hello -H "Destination:hello2" >> {"id":"hello2","rev":"1-1053fa7d0d20242aeeb1ba24ffd94977"} # получения всех документов curl -X GET http://localhost:5984/simple/_all_docs {"total_rows":2,"offset":0,"rows":[ {"id":"hello","key":"hello","value":{"rev":"2-493961fb199ea564f532de44d8be2650"}}, {"id":"hello2","key":"hello2","value":{"rev":"1-1053fa7d0d20242aeeb1ba24ffd94977"}} ]} # удаление документа curl -X DELETE http://localhost:5984/simple/hello2?rev=1-1053fa7d0d20242aeeb1ba24ffd94977 >> {"ok":true,"id":"hello2","rev":"2-72d1c6aa20574c9444eae3d90715a862"} # получение UUID curl -X GET http://localhost:5984/_uuids?count=1 >> {"uuids":["dbc21298bfb3f78ebe815cdb70014788"]}
View
Но вернемся к Fucon’у — настал черед создать view, идем в пункт “Temporary View…”:
Затем создаем map функцию:
function(doc) { emit(doc.title, doc.genre); }
Функция emit принимает в качестве параметров ключ и значение, данный параметры могут быть простыми, как в примере выше, или составными — массив или объект. Историю почему функция носит такое имя я рассказать не могу, зваться бы ей push
Cохраняем view как документ _design/books с именем genre — таки view это обычный документ в нашей БД, можем его даже пощупать:
{ "_id": "_design/books", "_rev": "1-532d13f2b2ef9dea495df8230f31b0bf", "language": "javascript", "views": { "genre": { "map": "function(doc) {\n emit(doc.title, doc.genre);\n}" } } }
Результатом нашей работы будет следующий набор данных:
// URL: http://localhost:5984/simple/_design/books/_view/genre // UTF я привел к русскому {"total_rows":5,"offset":0,"rows":[ {"id":"44","key":"Будущее, ХХ век. Исследователи","value":"fantastic"}, {"id":"55","key":"Винни-Пух","value":"child"}, {"id":"2","key":"Пикник на обочине","value":"fantastic"}, {"id":"3","key":"Трудно быть богом","value":"fantastic"}, {"id":"1","key":"Улитка на склоне","value":"fantastic"} ]}
Тем кто с SQL знаком
ORDER BY
CouchDB сортирует выборку по ключу передаваемому в функцию emit первым параметром. Следовательно, если нам надо отсортировать по названию книги, то map функцию придется изменить:
function(doc) { emit(doc.title, {title:doc.title, isbn:doc.ISBN}); }
Так же можно задать составной ключ:
function(doc) { emit([doc.genre, doc.title], {title:doc.title, isbn:doc.ISBN}); }
Для обратного порядка есть параметр ?descending=true
WHERE
Большинство логики ложится непосредственно на функцию map — именно она в ответе за условия поиска:
function(doc) { if (doc.authors.length > 1) emit(doc._id, {title:doc.title, isbn:doc.ISBN}); }
Но так же есть возможность простой фильтрации по ключу:
function(doc) { // ключом будет цена книги emit(doc.price, {title:doc.title, isbn:doc.ISBN}); }
Получить книги с определенной ценой: ?key=235
Получить все книги из диапазона цен: ?startkey=200&endkey=300
Если у нас составной ключ, то startkey и endkey должны быть составными: ?startkey=[100]&endkey=[300] (фильтр накладывается лишь на первый элемент составного ключа)
Если мы имеем дело с текстовым ключом, то можно получить конструкцию аналогичную LIKE “Ul%” используя следующий запрос: ?startkey=”ul”&endkey=”UL\ufff0″.
Порядок сортировки ключей следующий:
` ^ _ - , ; : ! ? . ' " ( ) [ ] { } @ * / \ & # % + < = > | ~ $ 0 1 2 3 4 5 6 7 8 9 a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O p P q Q r R s S t T u U v V w W x X y Y z Z
Больше информации о сортировке найдете в wiki: View сollation
LIMIT и OFFSET
Существуют следующие параметры ?limit=5&skip=10, вроде то что нужно, но, не ведитесь на эту провокацию, CouchDb все равно будет считывать пропущенные документы, но лишь отображать их не будет. Правильным является использование параметра startkey + limit. Алгоритм следующий:
- Выбираем необходимое количество элементов rows_per_page + еще один
- Отображаем rows_per_page элементов пользователю
- Запасной элемент (его ключ) используем для получения next_startkey (т.е. ссылки на следующую страницу)
- Ключ первого элемента используем для получения ссылки на предыдущую страницу
SUM, COUNT
Для подобных вычислений нам понадобится создать reduce функцию, именно она может обработать результаты работы map функции. Давайте вычислим количество книг:
// map function(doc) { // ключом будет жанр книги emit(doc._id, doc.title); } // reduce function(keys,values) { return values.length; } // результат {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;rows&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[ {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:null,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:4} ]} // можно еще упростить данный пример
Или лучше среднюю стоимость книг:
// map function(doc) { emit(doc._id, doc.price); } // reduce function(keys,values) { return Math.floor(sum(values)/values.length); } // результат {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;rows&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[ {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:null,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:233} ]}
Если надобности в reduce нет, то можно отключить, используя параметр ?reduce=false
У функции reduce есть еще третий параметр — rereduce. Насколько я понял, в том случае, когда у нас очень большая база, документы в reduce функцию будут поступать пачками, и пока rereduce=false у нас все хорошо, и функция работает как обычно, но когда rereduce=true то к нам на вход попадут в качестве values промежуточные данные вычислений.
GROUP BY
Для организации группировки нам понадобится reduce функция и параметр ?group=true. Приведу пример вычисления средней стоимости книг, с группировкой по жанру:
// map function(doc) { // ключом будет жанр книги emit(doc.genre, doc.price); } // reduce function(keys,values) { return Math.floor(sum(values)/values.length); } // результат {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;rows&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[ {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;child&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:145}, {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;fantastic&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:263} ]}
Так же возможна группировка по составному ключу, тут пригодится параметр ?group_level=1, с его помощью можно указывать уровень группировки, приведу пример из руководства:
// у нас есть следующие ключи [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1,1] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,3,4] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,3,8] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,2,6] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,2,6] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1,5] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,4,2]
При ?group_level=1 функция reduce будет запущена 3 раза, по разу для каждого из следующих сетов:
// set &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1,1] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,3,4] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,3,8] // set &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,2,6] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,2,6] // set &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1,5] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,4,2]
При ?group_level=2 функция reduce будет запущена 5 раз, по разу для каждого из следующих сетов:
// set &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, 1 [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1,1] // set &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, 3 [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,3,4] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;a&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,3,8] // set &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, 2 [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,2,6] [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,2,6] // set &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1 [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1,5] // set &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,4 [&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,4,2]
Возвращаясь к нашим книгам, то можно сделать следующий финт ушами (?group_level=2):
// map function(doc) { // первый параметр ключа - жанр, второй - цена в сотнях emit([doc.genre, Math.floor(doc.price/100)], doc.price); } // reduce function(keys,values) { return Math.floor(sum(values)/values.length); } // результат {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;rows&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[ {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;child&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1],&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:145}, {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;fantastic&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,2],&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:244}, {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;fantastic&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,3],&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:302} ]}
Связанные документы
Теперь надо бы добавить авторов книг, но как-то не очень хочется добавлять к каждому документу объект со всеми свойствами, так и хочется создать новую таблицу «пользователи», но у нас не та БД. По этой причине создаем новые документы с атрибутом type=author:
{ &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;name&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Alan Alexander Milne&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; }
В книгах добавляем type=book и ссылку на автора:
{ /*...*/ &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;authors&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: [ &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; ], &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;book&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; }
Теперь надо создать view, чтобы возвращались лишь книги:
function(doc) { if (doc.type != &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;book&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;) return; emit(doc._id, {title:doc.title, isbn:doc.ISBN}); }
Чтобы получить связанные документы (авторов из нашего примера) в функцию emit необходимо передать в качестве параметра объект следующего вида:
{ _id:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_2&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; }
А map функция приобретет следующий вид:
function(doc) { if (doc.type != &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;book&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;) return; emit([doc._id, 'book', 0], {title:doc.title, isbn:doc.ISBN}); if (doc.authors) { for (var i in doc.authors) { emit([doc._id, 'author', Number(i)+1], {_id:doc.authors[i]}) } } }
Но и этого мало, запрос при этом должен иметь вид http://localhost:5984/simple/_design/books/_view/all?include_docs=true:
{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;total_rows&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:14,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;offset&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:0,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;rows&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[ { &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1], &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_2&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;doc&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_2&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1-d39dd295bf1b91f27e329362a727eaf8&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;name&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Arcadiy Strugackiy&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;} }, { &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,2], &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_3&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;doc&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_3&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1-eeaadba334acb0afdd5d4d7a6eb3a11b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;name&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Boris Strugackiy&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;} }, { &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;doc&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,0], &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;title&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Улитка на склоне&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;isbn&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-5-17-057930-3&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-5-403-00677-4&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;]}, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;doc&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{ &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;8-1777bfc0f098b2382543ec5e48bc3b44&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;title&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Улитка на склоне&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;ISBN&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-5-17-057930-3&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-5-403-00677-4&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;], &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;genre&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;fantastic&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;price&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:235,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;book&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;authors&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_2&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_3&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;] } }, /* ... skip ... */ { &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;55&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;55&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,1], &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;doc&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;3-b3bce00a63620dbf4dd14b1337802e78&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;name&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Alan Alexander Milne&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;} }, { &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;55&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;55&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;doc&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,0], &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;title&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Винни-Пух&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;isbn&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-5-17-066600-3&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-5-271-27610-1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-985-16-8371-6&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;]}, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;doc&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{ &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;55&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;4-ea5acfb3800e0414bf20fb2a5150a586&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;title&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Винни-Пух&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;ISBN&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-5-17-066600-3&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-5-271-27610-1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;978-985-16-8371-6&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;], &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;genre&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;child&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;authors&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author_1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;],&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;book&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; } } ]}
Теперь остается собрать этот view в человеческий вид, и так хочется использовать reduce функцию, но вот беда, с параметром ?include_docs=true это невозможно.
Я не думаю, что это правильно и соответствует идеологии CouchDB, скорей всего стоит забыть о JOIN-подобных конструкциях
Немного о правах доступа
Если вы уже успели немного изучить интерфейс Fucon’а, то уже обратили внимание на кнопочку “Security”, нажимаем — в появившемся окошке можно установить имена/роли админов и пользователей (первые могут редактировать документы, вторые — не должен):
Если список reader’ов пуст, то БД считается публичной, и для чтения данных пароль не потребуется, иначе потребуется авторизация:
# получение документа + авторизация curl -X GET http://username:password@localhost:5984/simple/hello &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;hello&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1-f33e3c9b23e594593e93181763f0fb1b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;title&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Hello World!!!&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}
Собственно, мы редактируем документ db_name/_security, единственное отличие, он не имеет ревизии, дабы увеличить скорость работы авторизации
Поддерживаются следующие обработчики для авторизации:
- OAuth
- cookie
- default (используется для HTTP авторизации, описанной в RFC 2617)
Для каждого HTTP запроса запускаются все обработчики по очереди, как только кто-то опознает пользователя дальнейшее выполнение прерывается (очередность определяется конфигом).
Теперь можно попробовать создать несколько пользователей с разными ролями, Logout » Signup:
Результатом станет появление документа в БД _users:
{ &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;org.couchdb.user:guest&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1-9eeb0dcc95d472849590885bcc5f242c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;name&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;guest&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;salt&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;dbc21298bfb3f78ebe815cdb7000e180&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;password_sha&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;3d447e52765d76064223f501f16a9213d43a5ee5&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;user&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;roles&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: [] }
Побуем:
curl -X GET http://guest:123456@localhost:5984/simple/_all_docs &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;error&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;unauthorized&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;reason&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;You are not authorized to access this db.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}
Добавим ему роль reader и пробуем:
curl -X GET http://guest:123456@localhost:5984/simple/_all_docs &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;total_rows&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:1,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;offset&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:0,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;rows&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:[{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;hello&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;key&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;hello&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;value&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;4-2edebeac5a6e202b7d468c2d2fcd434c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}}]}
Теперь пробуем редактировать:
curl -X POST http://guest:123456@localhost:5984/simple -d '{&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;title&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Hello World!!!&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}' -H &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Content-Type:application/json&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; {&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;ok&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:true,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;dbc21298bfb3f78ebe815cdb7000eb31&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;:&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1-f33e3c9b23e594593e93181763f0fb1b&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}
Результат немного неожиданный, документ создали, хоть guest=reader. В действительности для разграничений прав доступа необходимо создать функцию валидации данных, и в ней организовывать логику разделения прав. Для этой цели создайте любой design документ с атрибутом validate_doc_update, в котором и будет описана наша функция:
{ &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_design/validate&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;4-e53c54b69a5916ddc06a4fd16f4b299d&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;validate_doc_update&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;function(new_doc, old_doc, userCtx) { /* code here */ }&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; }
К примеру, функция которая разрешает доступ лишь для редакторов будет иметь следующий вид:
function(new_doc, old_doc, userCtx) { if(userCtx.roles.indexOf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;editor&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;) === -1) { throw({unauthorized: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;You are not an editor.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;}); } }
Валидация
Прочитав предыдущий параграф возникает резонная мысль — значит CouchDB позволяет проверять данные перед занесением их в БД. О, да, и вот тому наглядный такой пример:
function(newDoc, oldDoc, userCtx) { function require(field, message) { message = message || &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Document must have a &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; + field; if (!newDoc[field]) throw({forbidden : message}); }; if (newDoc.type == &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;post&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;) { require(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;title&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;); require(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;created_at&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;); require(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;body&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;); require(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;author&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;); } if (newDoc.type == &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;comment&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;) { require(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;name&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;); require(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;created_at&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;); require(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;comment&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;You may not leave an empty comment&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;); } }
Хранение файлов
Отдельно стоит отметить, что файлы стоит хранить в самой CouchDB, так как хранятся они как есть на файловой системе, без всяких извращений, как то свойственно SQL. Из Fucon’а вы сможете легко добавить несколько аттачей к документу:
{ &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_id&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_rev&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;10-6754a5ad1330a6b8ebb544d052f2b03c&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;title&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Ulitka na sklone&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;_attachments&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: { &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;ulitka.jpeg&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: { // данный файл будет доступен по адресу http://localhost:5984/simple/1/ulitka.jpeg &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;content_type&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;image/jpeg&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;revpos&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: 10, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;length&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: 25227, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;stub&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;: true } } }
Документация
- Introduction to CouchDB views
- HTTP Document API
- HTTP view API
- View Snippets (полезные примеры)
- Recipes
- Security
- Validation
- Database Queries the CouchDB Way
Обзор CouchDB 1.0 и сравнение с версией 0.11, полезно почитать:
- Nice URLs with Rewrite Rules and Virtual Hosts
- Views; JOINs Redux, Raw Collation for Speed
- New Features in Replication
- Users, Authentication, Authorisation and Permissions
P.S. Есть чем дополнить/исправить — пишите комментарии.
Бум NoSQL )
У нас в сравнение CouchDB vs MongoDB по скорости победил MongoDB для наших задач (не на много, процентов на 10 в среднем). К преимуществам CouchDB я бы отнес возможность полностью реализовать бекенд, также в нем есть удобная для тестирования админка.
Кроме скорости есть еще и надежность.
Монга рушиться стоит питание компьютера отключить.
Антон, а можешь мне в двух словах (как для человека, ничего не знающего по теме noSQL) пояснить, в чем сакральный смысл использования этого самого noSQL? В JSON-запросах? Фишка, да, но как насчет бэкапов и дампов?
Я понимаю, что на все эти вопросы мне гугл ответит, но все же – в двух словах, а?
Присоединяюсь к вопросу Andrew и также хотелось бы услышать примеры использования. В каких случаях и проэктах NoSQL выигрывает у SQL БД? Гугл читал, но хотелось бы услышать и твою точку зрения.
Andrew, применительно к CouchDB – по идее легче его масштабировать и вроде как работает быстрее чем SQL т.к. данные для выдачи просчитываются уже при добавлении документа
2Сергей, как раз вот CouchDB и трудно масштабировать на несколько серверов, это не распределенная БД. Для масштабируемости лучше тогда взять Cassandra.
2dexter
Ну, как минимум, noSQL базу можно использовать в качестве вспомогательной.
Например для логирования, кеширования. REST API вполне позволяет нам выполнять логирование как на клиенте так и на сервере. Вот еще подумываю о сохранении состояния виджетов.
А вообще, по-моему, самое большое преимущество – это вложенные структуры данных и не фиксированный набор атрибутов которые может иметь сущность.
Возьмем связь 1:М. Вам нужно вывести построчно авторов и список книг для каждого автора.
В SQL придется вытаскивать список книг по отдельности для каждого автора.В noSQL, благодаря связанным объектам, подобный функционал реализуется намного быстрее и удобней.
Отсутствие фиксированного набора атрибутов, задаваемых при помощи DDL. Весьма удобно. От этого страдают все электронные магазины. Единственный способ в SQL – EAV архитектура.
Многие вещи выглядят намного естественней:
company.shpping_address = {addr1:…., addr2:…..}
company.billing_address = {……..}
company.phones = [phone1,phone2,phoneN]
Деревья? Пока не пробовал, но скоро придется. Опять же, в SQL есть несколько реализаций(Nested Sets, Adjacency List, Materialized Path). Но у них у всех огромные трудности со вставкой в середину и удалением из середины. Рискну попробовать Mongo или Couch.
На мой взгляд, основная проблема с noSQL в том, что мы очень привыкли мыслить таблицами а не структурами данных.
В принципе, все перечисленные задачи решаются при помощи традиционного и привычного нам SQL, вопрос лишь в том, насколько решение является простым и элегантным.
Я не особый спец в noSQL, но перечисленного мне вполне достаточно чтоб попробовать его в действии.
Именно что.
Переучиваться пора :-)
2Murzik, спасибо за примеры с применением noSQL.
Уже как бы sql даже не 10 лет, то действительно пора нам :)
На мой взгляд основная проблема реляционных баз данных и преимущество noSQL – это масштабируемость. Всегда самым узким местом будет мастер – который как правило один (или система получится очень сложной), и очень проблематично разбить одну таблицу (когда она ОЧЕНЬ большая) на несколько серверов. С подходом же map/reduce таких проблем не возникает.
Добрый день Антон.
Сейчас разбираюсь с CouchDB.
Ваша статья очень помогает. Спасибо Вам огромное.
Но у меня возникла проблема, не знаю даже куда копать.
Надеюсь Вы или кто-нибудь из читателей поможет.
Вобщем ситуация такова:
использую библиотеку jquery.couch.js (из стандартного дистрибутива).
Но при отправлении запроса на сервер, ответ возвращается пустой.
Код:
Было бы замечательно почитать хотя бы мини мануал по использованию этого плагина jquery.
Заранее спасибо.
С уважением, Роман.
to Roman
смотрели сообщения в консоле? если нет то посмотрите.
если у вас couchDB сервер сторонний, тоесть js исходники на одном хосте, дергают базу с другого хоста то нужно использовать JSONP , jquery.couch.js не подойдет. Если все на 127.0.0.1 попробуйте $.couch.urlPrefix = ”;
пример запроса
Вот статейку пихнул про CouchApp (http://habrahabr.ru/blogs/sandbox/110675/), я думаю, что в дополнение к этой будет неплохо понимать что к чему в приложениях.
Здравствуйте!
Может быть подскажите, каким образом можно сделать запрос к couchDB аналогичный SELECT * FROM table WHERE id IN (1, 2, 3)
также может быть Вы в курсе почему в официальной документации написано () “Since 0.9 you can also issue POST requests to views where you can send the following JSON structure in the body”, однако запрос ко view через POST не идет.
Спасибо
Одно не могу понять. За аттачили картинку в БД. А как ее потом от туда отобразить?
в бд хранится адрес её хранения. просто извлекаем адрес, и дёргаем её по этому адресу))
Вам не кажется, что Вы мыслите реляционными категориями?
Почему бы для авторов не завести новую базу? И вообще, для каждого “класса объектов” – свою? В CouchDB это делается “на раз”. Все равно, ведь, логика и связи реализуются на стороне приложения.
NoSQL – это гениально!!! Антон, огромное спасибо!!! Это самый внятный обзор, который нашёл в рунете)) скоро NoSQL захватит мир)) И вообще блог супер просто! Очень часто выручаешь)) И дизайн – ровно как надо сделан)) прям как я делать люблю)) Люди – это идеальный блог!!!
У меня возникла проблема(( Что то с консолью. Пытаюсь выполнить:
curl -X POST http://localhost:5984/simple -d ‘{“title”:”Hello World!!!”}’ -H “Content-Type:application/json”
выводит ошибку. Chrome пишет: “SyntaxError: Unexpected identifier”
ff c FireBug: “SyntaxError: missing ; before statement”
это печально(( мне ведь надо запросы отправлять!! Подскажите в чём тут дело? Что то я всё обыскал, и не могу найти ответа
Добрый день Алексей.
Экранировать надо кавычки:
хыы) всё разобрался )) надо было тупо в консоле писать т.е. /bin/bash а я в консоле браузера пытался) всё работает!!
Теперь правда не работает couth.jquery.js и главное нигде нет руководства, как с ним работать((
заметил печальный факт. в CouchDB нельзя обновить только одно свойство документа. необходимо посылать весь документ целиком, что крайне неудобно и ужасно для производительности. Чтобы вышесказанное было понятней приведу пример:
не то скопировал. вместо первой строки должно быть:
сути это не меняет. печально. в монго можно
Подскажыте пожалуста как решыть такую проблему.
Когда я передаю запрос типа 127.0.0.1:5984/base/_design/document/_view/get_users?keys=[3,4]
view:
function(doc) {
emit([doc.level, doc.alliance.index], doc);
}
Запрос выводит все записи в место записей, которые соответствуют [К1, К2]
Спасибо!
Спасибо, помог быстро понять что и куда. Статья не даром на первом месте по запросу “couchdb php” среди статей на русском.
Скажи а какие библиотеки вы используете у себя для связки PHP+CouchDB? Curl конечно хорошо для понимания основ, но в production его использовать имхо неудобно.
Если кто работал с metadata.js, можете подробно описать процесс подключения 1С к CouchDB?
У CouchDB же есть полноценный REST, не проще ли HTTP запросами рулить? Если у вас есть реальная интеграция 1С с CouchDB, будет полезно услышать как это сделали именно вы. Спасибо!