Repository: couchdb-nmo Updated Branches: refs/heads/master 2efbd3407 -> 0384eaee3
Add Mango Query support Able to create and query mango index Project: http://git-wip-us.apache.org/repos/asf/couchdb-nmo/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-nmo/commit/0384eaee Tree: http://git-wip-us.apache.org/repos/asf/couchdb-nmo/tree/0384eaee Diff: http://git-wip-us.apache.org/repos/asf/couchdb-nmo/diff/0384eaee Branch: refs/heads/master Commit: 0384eaee3043aa146bfce466ab0f664255fe85d4 Parents: 2efbd34 Author: Garren Smith <[email protected]> Authored: Wed Nov 18 12:18:00 2015 +0200 Committer: Garren Smith <[email protected]> Committed: Wed Jan 6 11:26:09 2016 +0200 ---------------------------------------------------------------------- doc/api/nmo-query.md | 27 ++++++++++ doc/cli/nmo-query.md | 17 ++++++ src/nmo.js | 3 +- src/query.js | 108 +++++++++++++++++++++++++++++++++++++ test/query.js | 134 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-nmo/blob/0384eaee/doc/api/nmo-query.md ---------------------------------------------------------------------- diff --git a/doc/api/nmo-query.md b/doc/api/nmo-query.md new file mode 100644 index 0000000..1901e5a --- /dev/null +++ b/doc/api/nmo-query.md @@ -0,0 +1,27 @@ +nmo-query(3) -- Create Mango indexes or query existing ones +==================================================== + +## SYNOPSIS + + nmo.commands.query.run([<clusterurl> || <cluster>], <database>, selector) + nmo.commands.query.createIndex([<clusterurl> || <cluster>], <database>, [fields]) + + +## DESCRIPTION + +### Query + +`nmo.commands.query.run` is used to query against the existing mango indexes. It accepts either a `cluster` name or a url to a cluster. It also requires the name of the database and a selector json object. + +e.g + + nmo.commands.query.run('mycluster', 'mydatabase', {selector: {"_id": {$gt: null}}}) + + +### Create Index + +`nmo.commands.query.createIndex` creates a new Mango index. It accepts either a `cluster` name or a url to a cluster. It also requires the name of the database and an array with the list of fields for the index + +e.g + + nmo.commands.createIndex('mycluster', 'mydatabase', ['name', 'age', 'location']) http://git-wip-us.apache.org/repos/asf/couchdb-nmo/blob/0384eaee/doc/cli/nmo-query.md ---------------------------------------------------------------------- diff --git a/doc/cli/nmo-query.md b/doc/cli/nmo-query.md new file mode 100644 index 0000000..01274ca --- /dev/null +++ b/doc/cli/nmo-query.md @@ -0,0 +1,17 @@ +nmo-query(1) -- Query Mango +=========================================== + +## SYNOPSIS + + nmo query cluster <database> create <fields> [--json] + nmo query cluster <database> <selector> [--json] + +## DESCRIPTION + +Create and query CouchDB Mango indexes. + +This will create an index with three fields + nmo query mycluster database create name,surname,age + +This will query an index + nmo query mycluster database '{"selector": {"_id": {"$gt":null}}}' http://git-wip-us.apache.org/repos/asf/couchdb-nmo/blob/0384eaee/src/nmo.js ---------------------------------------------------------------------- diff --git a/src/nmo.js b/src/nmo.js index 14bb01a..b5124b8 100644 --- a/src/nmo.js +++ b/src/nmo.js @@ -13,7 +13,8 @@ const commands = [ 'activetasks', 'savetofile', 'replicate-from', - 'replicate-to' + 'replicate-to', + 'query' ]; const nmo = { http://git-wip-us.apache.org/repos/asf/couchdb-nmo/blob/0384eaee/src/query.js ---------------------------------------------------------------------- diff --git a/src/query.js b/src/query.js new file mode 100644 index 0000000..02adbae --- /dev/null +++ b/src/query.js @@ -0,0 +1,108 @@ +import prettyjson from 'prettyjson'; +import nmo from './nmo.js'; +import Promise from 'bluebird'; +import { getUrlFromCluster, sendJsonToNode } from './utils'; + +export function cli (cluster, dbname, ...args) { + return new Promise((resolve, reject) => { + if (!cluster || !dbname || args.length === 0) { + const msg = [ + 'Usage:', + '', + 'nmo query <cluster> <database> create <fields> [--json]', + 'nmo query <cluster> <database> <selector> [--json]', + ].join('\n'); + + const err = new Error(msg); + err.type = 'EUSAGE'; + return reject(err); + } + + let promise; + + if (args[0] === 'create') { + if (!args[1] || !args[1].length === 0) { + const err = new Error('Please supply one or more fields to be indexed'); + err.type = 'EUSAGE'; + reject(err); + return; + } + + const fields = args[1].split(',').map(f => f.trim()); + promise = createIndex(cluster, dbname, fields) + } else { + let selector; + try { + eval('selector = ' + args[0]); + } catch (e) { + if (/Unexpected token/.test(e.message)) { + const err = new Error('Incorrect selector, it could be you used \" instead of \' for your selector'); + err.type = 'EUSAGE'; + return reject(err); + } + } + + if(!selector) { + const msg = [ + 'You have to wrap the selector in single quotes', + 'e.g nmo query anemone restaurants \'{"selector": {"_id": {"$gt":null}}}\'' + ].join('\n'); + const err = new Error(msg); + err.type = 'EUSAGE'; + return reject(err); + } + + promise = run(cluster, dbname, selector); + } + + promise + .then(resp => { + const jsonOut = nmo.config.get('json'); + + if (jsonOut) { + console.log(resp); + } else { + if (resp.result) { + if (resp.result === 'created') { + console.log('Index has been created.') + } else if (resp.result === 'exists') { + console.log('Index already exists.') + } else { + console.log(resp); + } + } else { + console.log(prettyjson.render(resp.docs)); + } + } + resolve(resp); + }) + .catch(err => { + reject(err); + }); + }); +} + +export function run (cluster, dbname, selector) { + return new Promise((resolve, reject) => { + const url = getUrlFromCluster(cluster) + '/' + dbname + '/_find'; + sendJsonToNode(url, selector) + .then(res => resolve(res)) + .catch(err => reject(err)); + }); +} + +export function createIndex(cluster, dbname, fields) { + return new Promise((resolve, reject) => { + const index = { + index: { + fields: fields + }, + type: 'json' + }; + + const url = getUrlFromCluster(cluster) + '/' + dbname + '/_index'; + sendJsonToNode(url, index) + .then(res => resolve(res)) + .catch(err => reject(err)); + }); +} http://git-wip-us.apache.org/repos/asf/couchdb-nmo/blob/0384eaee/test/query.js ---------------------------------------------------------------------- diff --git a/test/query.js b/test/query.js new file mode 100644 index 0000000..c439066 --- /dev/null +++ b/test/query.js @@ -0,0 +1,134 @@ +import assert from 'assert'; +import { consoleMock } from './helpers'; + +import { cli, run, createIndex } from '../src/query.js'; + +import * as common from './common.js'; +import nmo from '../src/nmo.js'; + +import nock from 'nock'; + +const nmoconf = {nmoconf: __dirname + '/fixtures/randomini'}; + +common.createConfigFile(); + +describe('Mongo Queries', () => { + beforeEach(() => { + return nmo.load(nmoconf); + }); + + describe('cli', () => { + + it('returns error on no value provided', () => { + return cli() + .catch((err) => { + assert.ok(err instanceof Error); + }); + }); + + it('returns error if selector is wrong', () => { + return cli('clusterone', 'dbname', '{selector: {:null}}') + .catch((err) => { + assert.ok(/Incorrect selector/.test(err.message)); + }); + }); + + it('returns error if selector is missing quotes', () => { + return cli('mycluster', 'dbname', '{selector:', ': {:null}}') + .catch((err) => { + assert.ok(/You have to/.test(err.message)); + }); + }); + + it('processes fields correctly', () => { + const index = { + index: { + fields: ['name', 'surname', 'boom'] + }, + type : 'json' + }; + + nock('http://127.0.0.1') + .post('/mydb/_index', index) + .reply(200, {result: 'created'}); + + return cli('clusterone', 'mydb', 'create', 'name, surname ,boom') + .then(resp => { + assert.deepEqual(resp.result, 'created'); + }); + }); + + it('warns if fields are missing', () => { + return cli('clusterone', 'mydb', 'create') + .catch(err => { + assert.ok(/Please supply/.test(err.message)); + }); + }); + + it('warns if fields are empty', () => { + return cli('clusterone', 'mydb', 'create', '') + .catch(err => { + assert.ok(/Please supply/.test(err.message)); + }); + }); + }); + + describe('run', () => { + it('requests find run correctly', () => { + const selector = {selector: {_id: 'one'}}; + + nock('http://127.0.0.1') + .post('/mydb/_find', selector) + .reply(200, {docs: [{id: 'one', rev: '123'}]}); + + return run('clusterone', 'mydb', selector) + .then(resp => { + assert.deepEqual(resp.docs.length, 1); + }); + }); + + it('returns error', () => { + var selector = {selector: {_id: 'one'}}; + + nock('http://127.0.0.1') + .post('/mydb/_find', selector) + .reply(500, {message: 'error'}); + + return run('clusterone', 'mydb', selector) + .catch(err => { + assert.deepEqual(err.type, 'EUSAGE'); + }); + }); + }); + + describe('createIndex', () => { + it('creates indexes', () => { + const index = { + index: { + fields: ['foo'] + }, + type : 'json' + }; + + nock('http://127.0.0.1') + .post('/mydb/_index', index) + .reply(200, {result: 'created'}); + + return createIndex('clusterone', 'mydb', ['foo']) + .then(resp => { + assert.deepEqual(resp.result, 'created'); + }); + }); + + it('returns error', () => { + nock('http://127.0.0.1') + .post('/mydb/_index') + .reply(500, {message: 'error'}); + + return createIndex('clusterone', 'mydb', []) + .catch(err => { + assert.deepEqual(err.type, 'EUSAGE'); + }); + }); + }); +});
