├── .gitignore ├── .travis.yml ├── utils.js ├── package.json ├── LICENSE ├── README.md ├── index.js └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swo 3 | *.swp 4 | .env 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "4.1" 5 | - "stable" 6 | addons: 7 | postgresql: "9.4" 8 | before_script: 9 | - psql -c 'create database pgjson_test;' -U postgres 10 | env: 11 | - TEST_DATABASE_URL=postgres:///pgjson_test 12 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dotToPostgresJSON: function (o) { 3 | var terms = o.split(/[\.[]/).map(function (t) { 4 | if (t.slice(-1)[0] == ']') { 5 | t = t.slice(0, -1) 6 | if (!isNaN(parseInt(t))) { 7 | return t 8 | } 9 | } 10 | return "'" + t + "'" 11 | }) 12 | return terms.join('->') 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pgjson", 3 | "version": "0.0.9", 4 | "description": "Postgres as a simple JSON document store.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "cuid": "*", 8 | "bluebird": "3.x", 9 | "pg-promise": "^4.3.3" 10 | }, 11 | "devDependencies": { 12 | "chai": "^3.3.0", 13 | "mocha": "^2.3.3" 14 | }, 15 | "scripts": { 16 | "test": "mocha" 17 | }, 18 | "author": "fiatjaf", 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/fiatjaf/pgjson.git" 23 | }, 24 | "keywords": [ 25 | "postgres", 26 | "postgresql", 27 | "couchdb", 28 | "key-value", 29 | "kv", 30 | "json", 31 | "redis", 32 | "mongodb", 33 | "nosql", 34 | "database", 35 | "store", 36 | "sql", 37 | "jsonb" 38 | ], 39 | "bugs": { 40 | "url": "https://github.com/fiatjaf/pgjson/issues" 41 | }, 42 | "homepage": "https://github.com/fiatjaf/pgjson#readme" 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 fiatjaf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PGJSON: start saving data to Postgres without thinking about schema 2 | 3 | Welcome to PGJSON! 4 | 5 | [![Build Status](https://travis-ci.org/fiatjaf/pgjson.svg?branch=master)](https://travis-ci.org/fiatjaf/pgjson) 6 | [![NPM Link](https://nodei.co/npm/pgjson.png)](https://npmjs.com/pgjson) 7 | 8 | A simple, zero-config, API for saving and retrieving JSON documents in a Postgres database. Just import and start using. 9 | 10 | ## Features 11 | 12 | * Support for PUT, POST, GET and DEL operations 13 | * Support for listing all docs and all `_ids` 14 | * Very basic querying API with filtering and ordering 15 | * You can start using this and later create indexes on specific JSON fields, write your own complex queries, create views or materialized views mixing the data in the `pgjson.main` table with other tables in the same database, or even export all data to other tables with formats and schemas, then forget about **pgjson**, Postgres is powerful and this library does not intend to stay between your data and all this power. 16 | 17 | ## Example 18 | 19 | ```javascript 20 | var Promise = require('bluebird') 21 | var db = new (require('pgjson'))('postgres:///db') 22 | 23 | Promise.resolve().then(function () { 24 | return db.post({ 25 | name: 'tomato', 26 | uses: ['tomato juice'] 27 | }) 28 | }) 29 | .then(function (res) { 30 | console.log(res) /* {ok: true, id: 'xwkfi23syw'} */ 31 | return db.get(res.id) 32 | }) 33 | .then(function (doc) { 34 | console.log(doc) /* {_id: 'xwkfi23syw', name: 'tomato', uses: ['tomato juice']} */ 35 | doc.uses.push('ketchup') 36 | doc.colour = 'red' 37 | return Promise.all([ 38 | db.put(doc), 39 | db.post([{name: 'banana', colour: 'yellow'}, {name: 'strawberry', colour: 'red'}]) 40 | ]) 41 | }) 42 | .then(function (res1, res2) { 43 | console.log(res1) /* {ok: true, id: 'xwkfi23syw'} */ 44 | console.log(res2) /* {ok: true, ids: ['xios83bndf', 'dx83hsalpw']} */ 45 | return db.query({ 46 | filter: 'colour = "red"', 47 | orderby: 'name', 48 | descending: true 49 | }) 50 | }) 51 | .then(function (docs) { 52 | console.log(docs) /* [{_id: 'xwkfi23syw', name: 'tomato', uses: ['tomato juice', 'ketchup'], colour: 'red'}, 53 | {_id: 'dx83hsalpw', name: 'strawberry', colour: 'red'}] */ 54 | return db.del(docs[0]._id) 55 | }) 56 | .catch(console.log.bind(console)) 57 | ``` 58 | 59 | In the meantime: 60 | 61 | ```sql 62 | postgres=> SELECT * FROM pgjson.main; 63 | id | doc 64 | ------------+------------------------------------------------------------------- 65 | xwkfi23syw | {"_id": "xwkfi23syw", "name": "tomato", "uses": ["tomato juice"]} 66 | (1 row) 67 | postgres=> 68 | postgres=> select * from pgjson.main 69 | ; 70 | id | doc 71 | ---------------------------+---------------------------------------------------------------------------------- 72 | xwkfi23syw | {"_id": "xwkfi23syw", "name": "tomato", "uses": ["tomato juice", "ketchup"]} 73 | xios83bndf | {"_id": "xios83bndf", "name": "banana", "colour": "yellow"} 74 | dx83hsalpw | {"_id": "dx83hsalpw", "name": "strawberry", "colour": "red"} 75 | (3 rows) 76 | 77 | ``` 78 | 79 | Basically this. 80 | 81 | --- 82 | 83 | ## API 84 | 85 | ### new PGJSON(options): DB 86 | 87 | `options` is anything [pg](https://github.com/brianc/node-postgres/wiki/Client#constructors) can take: a connection string, a domain socket folder or a config object. See the link for more details. 88 | 89 | ### DB.get(string _or_ array): Promise -> doc 90 | 91 | accepts the id, as string, of some document as a parameter, or an array of ids, and returns a promise for the raw stored JSON document. If passed an array of ids, the promise resolves to an array filled with the documents it could find, or null when it could not, in the correct order. If passed a single id and the target document is not found the promise resolves to `null`. 92 | 93 | ### DB.post(object _or_ array): Promise -> response 94 | 95 | accepts an object or an array of objects corresponding to all the documents you intend to create. Saves them with a random `._id` each and returns a promise for a response which will contain `{ok: true, id: }`. If an array of objects was passed, instead returns `{ok: true, ids: [, ...]}` with the ids in the correct order. If any passed object has an `_id` property this property will be discarded. 96 | 97 | ### DB.put(document): Promise -> response 98 | 99 | same as .post, but instead of creating a new document with a random id, this expects a complete _document_, which is to say an object with an `_id` property. Updates (or creates, if none is found) the document with the specified id. 100 | 101 | ### DB.del(string _or_ array): Promise -> response 102 | 103 | accepts an id, as string, or an array of strings corresponding to all the keys you want to delete. The response is an object of format `{ok: true}`. 104 | 105 | ### DB.query(query_params): Promise -> array 106 | 107 | accepts an object with the following optional parameters: 108 | 109 | * `filter`: a string specifying a condition to match with the document. The left condition should be the path of the attribute in the document; and the right a valid JSON value. Examples: 110 | * `'_id = "mellon"'` 111 | * `'properties.age' = 23` 112 | * `'children[2].name = "Jessica"'` 113 | * `orderby`: a string with the path of the desired attribute in the document. Examples: 114 | * `'name'` 115 | * `'items[0]'` 116 | * `'billing.creditcard.visa.n'` 117 | * `descending`: `true` or `false` -- default: `false` 118 | 119 | ### DB.allIds(): Promise -> array 120 | 121 | returns a promise to an array of ids (strings) of all documents stored in the database. 122 | 123 | ### DB.count(): Promise -> integer 124 | 125 | returns a promise to an integer with the count of all documents stored in the database. 126 | 127 | ### DB.purgeAll(): Promise -> null 128 | 129 | deletes everything: table, rows, schema. This isn't supposed to be used. 130 | 131 | ### DB.init(): Promise -> null 132 | 133 | creates a schema called _pgjson_, a table called _main_ and a function called _upsert_. This is idempotent and is called automatically when the DB is instantiated. 134 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird') 2 | var cuid = require('cuid') 3 | var utils = require('./utils') 4 | 5 | var pgp = require('pg-promise')({ 6 | promiseLib: Promise, 7 | query: process.env.DEBUG ? function (e) { console.log(e.query) } : undefined 8 | }) 9 | 10 | var handle = function (message) { 11 | return function (err) { 12 | console.log(message + ':', err) 13 | throw err 14 | } 15 | } 16 | 17 | var pgjson = (function () { 18 | function pgjson (cn) { 19 | this.db = pgp(cn) 20 | 21 | this.wait = Promise.resolve() 22 | this.init() 23 | } 24 | 25 | pgjson.prototype.post = function (doc) { 26 | if (Array.isArray(doc)) { 27 | return pgjson.prototype.postBulk.apply(this, arguments) 28 | } 29 | 30 | var db = this.db 31 | doc._id = cuid() 32 | 33 | return this.wait.then(function () { 34 | return db.one( 35 | 'INSERT INTO pgjson.main (id, doc) VALUES ($1, $2) RETURNING id', 36 | [doc._id, doc] 37 | ) 38 | }) 39 | .then(function (row) { 40 | return {ok: true, id: row.id} 41 | }) 42 | .catch(handle('problem posting doc')) 43 | } 44 | 45 | pgjson.prototype.postBulk = function (docs) { 46 | var db = this.db 47 | 48 | var ids = [] 49 | var values = docs.map(function (doc) { 50 | var id = cuid() 51 | ids.push(id) 52 | doc._id = id 53 | return pgp.as.format("($1, $2)", [id, doc]) 54 | }).join(',') 55 | return this.wait.then(function () { 56 | return db.none("INSERT INTO pgjson.main (id, doc) VALUES $1^", values) 57 | }).then(function () { 58 | return {ok: true, ids: ids} 59 | }).catch(handle('problem posting docs')) 60 | } 61 | 62 | pgjson.prototype.put = function (doc) { 63 | var db = this.db 64 | 65 | return this.wait.then(function () { 66 | return db.func('pgjson.upsert', [doc._id, doc]) 67 | }) 68 | .then(function () { 69 | return {ok: true, id: doc._id} 70 | }) 71 | .catch(handle('problem putting doc')) 72 | } 73 | 74 | pgjson.prototype.get = function (id) { 75 | if (Array.isArray(id)) { 76 | return pgjson.prototype.getBulk.apply(this, arguments) 77 | } 78 | 79 | var db = this.db 80 | return this.wait.then(function () { 81 | return db.oneOrNone('SELECT doc FROM pgjson.main WHERE id = $1', [id]) 82 | }) 83 | .then(function (row) { 84 | return row ? row.doc : null 85 | }) 86 | .catch(handle('problem getting doc')) 87 | } 88 | 89 | pgjson.prototype.getBulk = function (ids) { 90 | var db = this.db 91 | 92 | return this.wait.then(function () { 93 | return db.many('SELECT doc FROM unnest($1) WITH ORDINALITY AS u(id, ord) LEFT JOIN pgjson.main AS m ON m.id = u.id ORDER BY u.ord', [ids]) 94 | }) 95 | .then(function (rows) { 96 | return rows.map(function (r) { return r.doc }) 97 | }) 98 | .catch(handle('problem getting docs')) 99 | } 100 | 101 | pgjson.prototype.del = function (id) { 102 | var db = this.db 103 | return this.wait.then(function () { 104 | if (Array.isArray(id)) { 105 | return db.none('DELETE FROM pgjson.main WHERE id = ANY ($1)', [id]) 106 | } 107 | return db.none('DELETE FROM pgjson.main WHERE id = $1', [id]) 108 | }) 109 | .then(function () { 110 | return {ok: true} 111 | }) 112 | .catch(handle('problem deleting doc')) 113 | } 114 | 115 | pgjson.prototype.query = function (opts) { 116 | opts = opts || {} 117 | var db = this.db 118 | return this.wait.then(function () { 119 | var params = { 120 | limit: opts.limit || 1000, 121 | offset: opts.offset || 0, 122 | order: opts.descending ? 'DESC' : 'ASC', 123 | criteria: "'_id'", 124 | where: "'1'", 125 | condition: '1' 126 | } 127 | 128 | // orderby 129 | if (opts.orderby) { 130 | params.criteria = utils.dotToPostgresJSON(opts.orderby) 131 | } 132 | 133 | // filter 134 | if (opts.filter) { 135 | var expr = opts.filter.split('=') 136 | params.where = pgp.as.format('doc->$1^', utils.dotToPostgresJSON(expr[0].trim())) 137 | params.condition = JSON.stringify(JSON.parse(expr[1].trim())) 138 | } 139 | 140 | return db.any("SELECT doc FROM pgjson.main WHERE ${where^} = ${condition} ORDER BY doc->${criteria^} ${order^}, doc->'_id' ${order^} LIMIT ${limit} OFFSET ${offset}", params) 141 | }) 142 | .then(function (rows) { 143 | return rows.map(function (r) { return r.doc }) 144 | }) 145 | } 146 | 147 | pgjson.prototype.count = function () { 148 | var db = this.db 149 | 150 | return this.wait.then(function () { 151 | return db.one('SELECT count(*) AS c FROM pgjson.main') 152 | }) 153 | .then(function (row) { 154 | return row.c 155 | }) 156 | .catch(handle('problem counting docs')) 157 | } 158 | 159 | pgjson.prototype.listIds = function () { 160 | var db = this.db 161 | 162 | return this.wait.then(function () { 163 | return db.any('SELECT id FROM pgjson.main') 164 | }) 165 | .then(function (rows) { 166 | return rows.map(function (r) { return r.id }) 167 | }) 168 | .catch(handle('problem listing ids')) 169 | } 170 | 171 | pgjson.prototype.init = function () { 172 | var db = this.db 173 | 174 | this.wait = this.wait.then(function () { 175 | return db.query('CREATE SCHEMA IF NOT EXISTS pgjson') 176 | }).then(function () { 177 | return db.query('CREATE TABLE IF NOT EXISTS pgjson.main ( id text PRIMARY KEY, doc jsonb )') 178 | }) 179 | .then(function () { 180 | return db.query( 181 | 'CREATE OR REPLACE FUNCTION pgjson.upsert(key text, data jsonb) \n\ 182 | RETURNS VOID AS \n\ 183 | $$ \n\ 184 | BEGIN \n\ 185 | LOOP \n\ 186 | UPDATE pgjson.main SET doc = data WHERE id = key; \n\ 187 | IF found THEN \n\ 188 | RETURN; \n\ 189 | END IF; \n\ 190 | BEGIN \n\ 191 | INSERT INTO pgjson.main(id, doc) VALUES (key, data); \n\ 192 | RETURN; \n\ 193 | EXCEPTION WHEN unique_violation THEN \n\ 194 | END; \n\ 195 | END LOOP; \n\ 196 | END; \n\ 197 | $$ \n\ 198 | LANGUAGE plpgsql \n\ 199 | ' 200 | ) 201 | }) 202 | .catch(handle('could not initialize pgjson schema, table and functions')) 203 | 204 | return this.wait 205 | } 206 | 207 | pgjson.prototype.purgeAll = function () { 208 | var db = this.db 209 | 210 | this.wait = this.wait.then(function () { 211 | return db.query('DROP SCHEMA pgjson CASCADE') 212 | }) 213 | .catch(handle('problem purging schema')) 214 | 215 | return this.wait 216 | } 217 | 218 | return pgjson 219 | })() 220 | 221 | module.exports = pgjson 222 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert 2 | var Promise = require('bluebird') 3 | var cuid = require('cuid') 4 | var PGJSON = require('..') 5 | 6 | describe('basic functions', function () { 7 | var pj 8 | 9 | before(function () { 10 | pj = new PGJSON(process.env.TEST_DATABASE_URL) 11 | }) 12 | 13 | beforeEach(function () { 14 | pj.purgeAll() 15 | pj.init() 16 | }) 17 | 18 | describe('CRUD', function () { 19 | it('should create a new doc', function (done) { 20 | pj.post({ 21 | banana: 23, 22 | goiaba: ['3a4', {ra: 17}] 23 | }).then(function (res) { 24 | assert.equal(res.ok, true, 'post succeeded.') 25 | assert.lengthOf(res.id, cuid().length, 'generated id is a cuid.') 26 | }).then(done).catch(done) 27 | }) 28 | 29 | it('should create and read docs', function (done) { 30 | var initial = { 31 | _id: 'goiabinha', 32 | uva: 41 33 | } 34 | pj.put(initial) 35 | .then(function (r) { 36 | assert.deepEqual(r, {ok: true, id: 'goiabinha'}, 'put succeeded.') 37 | return pj.get('goiabinha') 38 | }).then(function (doc) { 39 | assert.deepEqual(initial, doc, 'read after put succeeded.') 40 | }).then(done).catch(done) 41 | }) 42 | 43 | it('should create, count, update, list and delete docs', function (done) { 44 | var banana = {cor: 'amarela'} 45 | var uva = {cor: 'grená'} 46 | 47 | pj.post(banana) 48 | .then(function (bu) { 49 | assert.deepEqual(banana, { 50 | cor: 'amarela', 51 | _id: bu.id 52 | }) 53 | return pj.count() 54 | }) 55 | .then(function (c) { 56 | assert.equal(c, 1) 57 | return pj.post(banana) 58 | }) 59 | .then(function (bu) { 60 | var newbanana = { 61 | _id: bu.id, 62 | cor: 'amarela pintada', 63 | tipo: 'fruta', 64 | 'açúcar': 26, 65 | faz: ['sorvete', 'bolo'] 66 | } 67 | return pj.put(newbanana) 68 | }) 69 | .then(function (bu) { 70 | return pj.get(bu.id) 71 | }) 72 | .then(function (newbanana) { 73 | delete newbanana._id 74 | assert.deepEqual(newbanana, { 75 | cor: 'amarela pintada', 76 | tipo: 'fruta', 77 | 'açúcar': 26, 78 | faz: ['sorvete', 'bolo'] 79 | }) 80 | return pj.post(uva) 81 | }) 82 | .then(function () { 83 | return pj.count() 84 | }) 85 | .then(function (c) { 86 | assert.equal(c, 3) 87 | return pj.listIds() 88 | }) 89 | .then(function (ids) { 90 | return Promise.all(ids.map(function (id) { 91 | return pj.del(id) 92 | })) 93 | }) 94 | .then(function (deletes) { 95 | deletes.forEach(function (d) { 96 | assert.equal(d.ok, true) 97 | }) 98 | assert.lengthOf(deletes, 3, 'deleted everything correctly.') 99 | }).then(done).catch(done) 100 | }) 101 | 102 | it('fetching inexistent doc', function (done) { 103 | pj.get('nothing') 104 | .then(function (doc) { 105 | assert.equal(doc, null) 106 | }).then(done).catch(done) 107 | }) 108 | }) 109 | 110 | describe('transactions', function () { 111 | it('should bulk create, then bulk fetch, then bulk delete', function (done) { 112 | var ids 113 | 114 | pj.post([{fruit: 'banana'}, {fruit: 'passion'}, {fruit: 'goiaba'}]) 115 | .then(function (res) { 116 | assert.equal(res.ok, true) 117 | assert.equal(res.ids.length, 3, 'posted three docs at the same time and got three ids.') 118 | ids = res.ids 119 | return pj.get(res.ids) 120 | }) 121 | .then(function (docs) { 122 | assert.equal(docs.length, 3) 123 | assert.deepEqual(docs.map(function (d) { return d.fruit }), ['banana', 'passion', 'goiaba']) 124 | assert.deepEqual(docs.map(function (d) { return d._id }), ids, 'successfully fetched the three docs in order') 125 | return pj.get([ids[0], 'none', ids[1]]) 126 | }) 127 | .then(function (docs) { 128 | assert.equal(docs[0].fruit, 'banana') 129 | assert.equal(docs[1], null, 'got null for a doc id which could not be found.') 130 | assert.equal(docs[2].fruit, 'passion') 131 | return pj.del(ids) 132 | }) 133 | .then(function (deletes) { 134 | assert.equal(deletes.ok, true) 135 | }).then(done).catch(done) 136 | }) 137 | }) 138 | 139 | describe('fancy queries', function () { 140 | it('should fetch all docs, sorted by id', function (done) { 141 | Promise.all([{_id: 'b', word: 'banana'}, {_id: 'a', word: 'azul'}].map(function (doc) { 142 | return pj.put(doc) 143 | })) 144 | .then(function () { 145 | return pj.query() 146 | }) 147 | .then(function (docs) { 148 | assert.deepEqual(docs, [{_id: 'a', word: 'azul'}, {_id: 'b', word: 'banana'}]) 149 | }).then(done).catch(done) 150 | }) 151 | 152 | it('should sort by any key and limit', function (done) { 153 | Promise.all([ 154 | {_id: 'a', word: 'laranja', props: {coolness: 23}, j: false}, 155 | {_id: 'b', word: 'arab', props: {coolness: 18}, letters: ['a', 'r']}, 156 | {_id: 'c', word: 'laec', props: {}, j: false, letters: ['l', 'a']}, 157 | {_id: 'd', word: 'jalad', j: true, letters: ['j', 'a']} 158 | ].map(function (doc) { return pj.put(doc) })) 159 | .then(function () { 160 | return pj.query({orderby: 'word'}) 161 | }) 162 | .then(function (docs) { 163 | var ids = docs.map(function (d) { return d._id }) 164 | assert.deepEqual(ids, ['b', 'd', 'c', 'a']) 165 | return pj.query({orderby: 'props.coolness', limit: 3}) 166 | }) 167 | .then(function (docs) { 168 | var ids = docs.map(function (d) { return d._id }) 169 | assert.deepEqual(ids, ['b', 'a', 'c']) 170 | return pj.query({orderby: 'j', descending: true, limit: 2, offset: 1}) 171 | }) 172 | .then(function (docs) { 173 | var ids = docs.map(function (d) { return d._id }) 174 | assert.deepEqual(ids, ['d', 'c']) 175 | return pj.query({orderby: 'letters[1]', descending: true, offset: 3, limit: 2}) 176 | }) 177 | .then(function (docs) { 178 | var ids = docs.map(function (d) { return d._id }) 179 | assert.deepEqual(ids, ['c']) 180 | }).then(done).catch(done) 181 | }) 182 | 183 | it('should filter the docs based on some conditions', function (done) { 184 | Promise.all([ 185 | {_id: 'a', word: 'laranja', props: {coolness: 23}, j: false}, 186 | {_id: 'b', word: 'arab', props: {coolness: 18}, letters: ['a', 'r']}, 187 | {_id: 'c', word: 'laec', props: {}, j: false, letters: ['l', 'a']}, 188 | {_id: 'd', word: 'jalad', j: true, letters: ['j', 'a']}, 189 | {_id: 'e', word: 'olie', props: {coolness: 18}, letters: ['r', 'l', 'y']}, 190 | ].map(function (doc) { return pj.put(doc) })) 191 | .then(function () { 192 | return pj.query({filter: 'word = "laranja"'}) 193 | }) 194 | .then(function (docs) { 195 | var ids = docs.map(function (d) { return d._id }) 196 | assert.deepEqual(ids, ['a']) 197 | return pj.query({filter: 'props.coolness = 18', orderby: 'letters[1]'}) 198 | }) 199 | .then(function (docs) { 200 | var ids = docs.map(function (d) { return d._id }) 201 | assert.deepEqual(ids, ['e', 'b']) 202 | return pj.query({orderby: 'j', descending: true, filter: 'j = true'}) 203 | }) 204 | .then(function (docs) { 205 | var ids = docs.map(function (d) { return d._id }) 206 | assert.deepEqual(ids, ['d']) 207 | return pj.query({filter: 'props = {"coolness": 23}'}) 208 | }) 209 | .then(function (docs) { 210 | var ids = docs.map(function (d) { return d._id }) 211 | assert.deepEqual(ids, ['a']) 212 | }).then(done).catch(done) 213 | }) 214 | }) 215 | }) 216 | --------------------------------------------------------------------------------