List-функции в CouchDB

в 1:53, , рубрики: couchdb, nosql, метки:

На Хабре часто встречается комментарий о том, что документацию разработчики не дочитывают до конца. Столкнулся с этим сам, когда открыл для себя 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


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js