На Хабре часто встречается комментарий о том, что документацию разработчики не дочитывают до конца. Столкнулся с этим сам, когда открыл для себя List-функции в CouchDB.
Мне показался вопрос достаточно сложным и не очень хорошо объясненным в документации, решил поделиться с уважаемым сообществом своим исследованием.
List-функции в design-документах CouchDB нужны для того, чтобы иметь возможность обработать всю базу данных одной функцией. Т.е. это некий аналог Full Table Scan в реляционных базах.
Рассмотрим design-документ из реально работающей инсталляции CouchDB.
{
"_id": "_design/complete",
"_rev": "2-45c7b0280b529d99b1d34f362e457860",
"views": {
"freq": {
"map": "function(doc) { emit(doc.REQUEST, 1);}",
"reduce": "function (key, values, rereduce){return sum(values);}"
}
},
"lists": {
"basicJSON": "function(head, req) { start({headers :{'Content-Type' : 'text/plain;charset=utf-8'}}); send('{"head":'+toJSON(head)+', ');send('"req":'+toJSON(req)+', ');send('"rows":[');var row;var prev = null;while (row = getRow()){if (prev != null && prev.key == row.key) {} else {if (prev != null) { send(',');} send(toJSON({id: row.id, key: row.key}));} prev = row;} send(']}');}"
}
}
CouchDB сильно не любит переводы каретки в функциях, поэтому все функции идут в одну строку. Для лучшей читаемости разверну функцию basicJSON в lists:
function (head, req) {
start({
headers: {
'Content-Type': 'text/plain;charset=utf-8'
}
});
send('{"head":' + toJSON(head) + ', ');
send('"req":' + toJSON(req) + ', ');
send('"rows":[');
var row;
var prev = null;
while (row = getRow()) {
if (prev != null && prev.key == row.key) {} else {
if (prev != null) {
send(',');
}
send(toJSON({
id: row.id,
key: row.key
}));
}
prev = row;
}
send(']}');
Что здесь интересного?
Ключевым звеном является цикл обработки:
var row;
...
while (row = getRow()) {
...
send(',');
...
send(toJSON({
id: row.id,
key: row.key
}));
...
}
Поскольку мы работаем с HTTP, когда обращаемся к CouchDB, то list-функция строит http-ответ. Т.е. по сути генерирует текст в какой-то кодировке. За это отвечает фукнция send(); Она возвращает текстовую строку в http-ответ сервера. Можно использовать конструкцию send(toJSON()); для возврата текстового представления JSON-объектов.
getRow() получает следующую запись в БД. Собственно, на её основе формируется цикл обработки.
Функция start отвечает за формирование заголовка http-ответа.
Вызывается приведённая list-функция так:
http://localhost:5984/requests-db/_design/complete/_list/basicJSON/freq?reduce=false
Указывается имя БД, design-документ, _list, название функции списка, представление. Затем идут параметры представления. Параметры можно указывать так же, как при обращении к любому представлению.
Таким образом, с помощью list-функций можно произвести нужную обработку данных, такую, которая не всегда доступна с помощью Map/Reduce или будет очень сложной на клиенте. С помощью условий в цикле можно часть записей отсеивать, а можно на одну запись делать несколько вызовов send(), что приведет к увеличению количества записей в ответе.
В итоге строится http-текст, содержащий все записи в БД, прошедшие через функциональную обработку.
Да, конечно, это не очень быстрая обработка, но ведь и в реляционных БД full table scan — это самая медленная операция.
List-функции, это очень удобный и полезный механизм, до которого многие не дочитывают в документации. По крайней мере я.
Хорошего вам кода!
PS. В CouchDB есть еще show-функции, но про это как-нибудь в другой раз.
Автор: DmitrySolomennikov