Статьи

CouchDB: сценарии оболочки JSON с Node.js

Содержание этой статьи было первоначально написано Барендом Гарвелинком в блоге Xebia .

 

В настоящее время я работаю в команде проекта, работающей над приложением, которое хранит большую часть своих данных в CouchDB . Одна из замечательных особенностей Couch — его RESTful API. Это все просто HTTP и JSON, легко понять и легко программировать.

Одним из аспектов, где этот интерфейс не так легко доступен, является сценарий оболочки. Существует завиток для обработки всех HTTP-вещей, которые нам могут понадобиться, но преобразование структуры JSON или извлечение информации из нее оказалось менее простым. Мы можем охватить простые случаи с помощью grep и awk, но JSON достаточно сложен, чтобы мы (или, ну, я) не захотели. Если бы документы были в формате XML, мы могли бы использовать xpath и xslt для выполнения нашей тяжелой работы. Насколько мне известно, нет эквивалента xmlstarlet для JSON, который бы надежно справлялся с этой работой.

Мы решили нашу проблему сценариев оболочки, и решение совершенно очевидно, потому что есть отличный DSL для управления структурами JSON. Это называется JavaScript :-).

Наши сценарии администрирования используют Node.js в качестве стадии конвейера. Существует так много блогов и статей, в которых говорится о Node как о серверной платформе, поэтому легко забыть, что это универсальный интерпретатор JavaScript и REPL.

Простой пример

Этот скрипт читает представление CouchDB, в котором перечислены документы, от которых мы хотим избавиться. В этом примере я использовал стандартное представление _all_docs, которое доступно в каждой базе данных Couch. Затем он обрабатывает этот список в JavaScript, чтобы создать команду удаления для массового API CouchDB . Затем команда выполняется, и объект ответа печатается с помощью jsonpp .

COUCH="http://localhost:5984/dbname"
curl "${COUCH}/_all_docs&include_docs=true" \
  | ./format_delete_command.js \
  | curl --header 'Content-Type: application/json' \
         --header 'Accept: application/json' \
         -X POST --data-binary @- "${COUCH}/_bulk_docs" \
  | tr -d '\n' | jsonpp

Для тех, кто не знаком с curl: параметр -data-binary указывает тело запроса. Знак @ указывает, что ниже следует имя файла. Для публикации из stdin используйте знак — в качестве имени файла. Без знака at значение параметра используется непосредственно как тело запроса.

Когда мы читаем из этого представления CouchDB, ответ выглядит примерно так:

{"total_rows":22,"offset":0,"rows":[
{"id":"123456","key":"123456","value":{"rev":"1-acf7f3..."},"doc":{...}},
{"id":"123457","key":"123457","value":{"rev":"1-b67df1..."},"doc":{...}}
...20 more...
]}

Обратите внимание, что поле doc пропущено, если вы не передадите include_docs = true в качестве параметра запроса.

Чтобы отправить команду массового удаления, мы отправляем это:

{ "docs":[
{"_id":"123456","_rev":"1-acf6f39495a2cd4465be504cd435629e","_deleted":true}
{"_id":"123457","_rev":"1-b67df18954264dbb65be341294e572a5","_deleted":true}
...20 more...
]}

awk мог бы справиться с этим. Вот JavaScript, который мы используем вместо:

#! /usr/bin/env node
var inputStream = process.stdin
  , data = '';

process.stdin.resume();

// Read the entire input stream into the data variable.
inputStream.on('data', function(chunk) {
  data += chunk;
});

// At end of stream, load the JSON object and process it.
inputStream.on('end', function() {
  var json = JSON.parse(data)
    , rows = json['rows']
    , postdata = { docs: [] };

  rows.forEach(function(row) {
    var doc = row['doc'];
    postdata.docs.push( {_id: doc._id, _rev: doc._rev, _deleted: true} );
  });

  console.log(JSON.stringify(postdata));
});

Несколько вещей, на которые стоит обратить внимание:

  • Node.js достаточно умен, чтобы игнорировать строку hashbang.
  • Мы просто буферизируем весь ввод во временную переменную. Наши документы маленькие, нам не нужен умный потоковый парсер.
  • Используйте console.log для записи в стандартную строку, console.warn для записи в стандартную ошибку. Вы можете использовать process.exit (int) для выхода с кодом ошибки.
  • Для этого конкретного представления у нас были идентификаторы и обороты в выводе представления, и поэтому мы могли бы отформатировать команду удаления без передачи параметра include_docs. Это потому, что я использовал представление _all_docs для примера. Наши фактические представления имеют различный вывод, и использование _all_docs заставляет скрипт работать со всеми из них. (Пожалуйста, не передавайте свой _all_docs вид через этот скрипт, просто удалите базу данных!)

Другие наши скрипты используют тот же подход.

Резюме

Node.js так часто называют сервером, что легко забыть, что вы можете использовать его в конвейере оболочки. Этот пост демонстрирует, как вы можете сделать это.

Обновлено 2012-06-27:
Вы можете проверить мой следующий пост, сценарии оболочки JSON с jsawk .