/* global sum */ 'use strict'; var PouchDB = require('./pouchdb'); var should = require('chai').should(); var testUtils = require('./test.utils.js'); var adapters = ['local']; adapters.forEach(function (adapter) { var viewTypes = ['persisted', 'temp']; viewTypes.forEach(function (viewType) { var suiteName = 'test.mapreduce.js-' + adapter + '-' + viewType; var dbName = testUtils.adapterUrl(adapter, 'testdb'); tests(suiteName, dbName, adapter, viewType); }); }); function tests(suiteName, dbName, dbType, viewType) { describe(suiteName, function () { var Promise; var createView; if (viewType === 'persisted') { createView = function (db, viewObj) { var storableViewObj = { map : viewObj.map.toString() }; if (viewObj.reduce) { storableViewObj.reduce = viewObj.reduce.toString(); } return new Promise(function (resolve, reject) { db.put({ _id: '_design/theViewDoc', views: { 'theView' : storableViewObj } }, function (err) { if (err) { reject(err); } else { resolve('theViewDoc/theView'); } }); }); }; } else { createView = function (db, viewObj) { return new Promise(function (resolve) { setTimeout(function () { resolve(viewObj); }); }); }; } beforeEach(function () { Promise = PouchDB.utils.Promise; return new PouchDB(dbName).destroy(); }); afterEach(function () { return new PouchDB(dbName).destroy(); }); it("Test basic view", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo, doc); } }).then(function (view) { return db.bulkDocs({docs: [ {foo: 'bar'}, { _id: 'volatile', foo: 'baz' } ]}).then(function () { return db.get('volatile'); }).then(function (doc) { return db.remove(doc); }).then(function () { return db.query(view, {include_docs: true, reduce: false}); }).then(function (res) { res.rows.should.have.length(1, 'Dont include deleted documents'); res.total_rows.should.equal(1, 'Include total_rows property.'); res.rows.forEach(function (x) { should.exist(x.id); should.exist(x.key); should.exist(x.value); should.exist(x.value._rev); should.exist(x.doc); should.exist(x.doc._rev); }); }); }); }); }); it("Test basic view, no emitted value", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); } }).then(function (view) { return db.bulkDocs({docs: [ {foo: 'bar'}, { _id: 'volatile', foo: 'baz' } ]}).then(function () { return db.get('volatile'); }).then(function (doc) { return db.remove(doc); }).then(function () { return db.query(view, {include_docs: true, reduce: false}); }).then(function (res) { res.rows.should.have.length(1, 'Dont include deleted documents'); res.total_rows.should.equal(1, 'Include total_rows property.'); res.rows.forEach(function (x) { should.exist(x.id); should.exist(x.key); should.equal(x.value, null); should.exist(x.doc); should.exist(x.doc._rev); }); }); }); }); }); if (dbType === 'local' && viewType === 'temp') { it("with a closure", function () { return new PouchDB(dbName).then(function (db) { return db.bulkDocs({docs: [ {foo: 'bar'}, { _id: 'volatile', foo: 'baz' } ]}).then(function () { var queryFun = (function (test) { return function (doc, emit) { if (doc._id === test) { emit(doc.foo); } }; }('volatile')); return db.query(queryFun, {reduce: false}); }); }).should.become({ total_rows: 1, offset: 0, rows: [ { id: 'volatile', key: 'baz', value: null } ] }); }); } if (viewType === 'temp') { it('Test simultaneous temp views', function () { return new PouchDB(dbName).then(function (db) { return db.put({_id: '0', foo: 1, bar: 2, baz: 3}).then(function () { return Promise.all(['foo', 'bar', 'baz'].map(function (key, i) { var fun = 'function(doc){emit(doc.' + key + ');}'; return db.query({map: fun}).then(function (res) { res.rows.should.deep.equal([{ id: '0', key: i + 1, value: null }]); }); })); }); }); }); it("Test passing just a function", function () { return new PouchDB(dbName).then(function (db) { return db.bulkDocs({docs: [ {foo: 'bar'}, { _id: 'volatile', foo: 'baz' } ]}).then(function () { return db.get('volatile'); }).then(function (doc) { return db.remove(doc); }).then(function () { return db.query(function (doc) { emit(doc.foo, doc); }, {include_docs: true, reduce: false}); }).then(function (res) { res.rows.should.have.length(1, 'Dont include deleted documents'); res.rows.forEach(function (x) { should.exist(x.id); should.exist(x.key); should.exist(x.value); should.exist(x.value._rev); should.exist(x.doc); should.exist(x.doc._rev); }); }); }); }); } it("Test opts.startkey/opts.endkey", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.key, doc); } }).then(function (queryFun) { return db.bulkDocs({docs: [ {key: 'key1'}, {key: 'key2'}, {key: 'key3'}, {key: 'key4'}, {key: 'key5'} ]}).then(function () { return db.query(queryFun, {reduce: false, startkey: 'key2'}); }).then(function (res) { res.rows.should.have.length(4, 'Startkey is inclusive'); return db.query(queryFun, {reduce: false, endkey: 'key3'}); }).then(function (res) { res.rows.should.have.length(3, 'Endkey is inclusive'); return db.query(queryFun, { reduce: false, startkey: 'key2', endkey: 'key3' }); }).then(function (res) { res.rows.should.have.length(2, 'Startkey and endkey together'); return db.query(queryFun, { reduce: false, startkey: 'key4', endkey: 'key4' }); }).then(function (res) { res.rows.should.have.length(1, 'Startkey=endkey'); }); }); }); }); it("#4154 opts.start_key/opts.end_key are synonyms", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.key, doc); } }).then(function (queryFun) { return db.bulkDocs({docs: [ {key: 'key1'}, {key: 'key2'}, {key: 'key3'}, {key: 'key4'}, {key: 'key5'} ]}).then(function () { return db.query(queryFun, {reduce: false, start_key: 'key2'}); }).then(function (res) { res.rows.should.have.length(4, 'Startkey is inclusive'); return db.query(queryFun, {reduce: false, end_key: 'key3'}); }).then(function (res) { res.rows.should.have.length(3, 'Endkey is inclusive'); return db.query(queryFun, { reduce: false, start_key: 'key2', end_key: 'key3' }); }).then(function (res) { res.rows.should.have.length(2, 'Startkey and endkey together'); return db.query(queryFun, { reduce: false, start_key: 'key4', end_key: 'key4' }); }).then(function (res) { res.rows.should.have.length(1, 'Startkey=endkey'); }); }); }); }); //TODO: split this to their own tests within a describe block it("Test opts.inclusive_end = false", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.key, doc); } }).then(function (queryFun) { return db.bulkDocs({docs: [ {key: 'key1'}, {key: 'key2'}, {key: 'key3'}, {key: 'key4'}, {key: 'key4'}, {key: 'key5'} ]}).then(function () { return db.query(queryFun, { reduce: false, endkey: 'key4', inclusive_end: false }); }).then(function (resp) { resp.rows.should.have.length(3, 'endkey=key4 without ' + 'inclusive end'); resp.rows[0].key.should.equal('key1'); resp.rows[2].key.should.equal('key3'); }) .then(function () { return db.query(queryFun, { reduce: false, startkey: 'key3', endkey: 'key4', inclusive_end: false }); }).then(function (resp) { resp.rows.should.have.length(1, 'startkey=key3, endkey=key4 ' + 'without inclusive end'); resp.rows[0].key.should.equal('key3'); }).then(function () { return db.query(queryFun, { reduce: false, startkey: 'key4', endkey: 'key1', descending: true, inclusive_end: false }); }).then(function (resp) { resp.rows.should .have.length(4, 'startkey=key4, endkey=key1 descending without ' + 'inclusive end'); resp.rows[0].key.should.equal('key4'); }); }); }); }); it("Test opts.key", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.key, doc); } }).then(function (queryFun) { return db.bulkDocs({docs: [ {key: 'key1'}, {key: 'key2'}, {key: 'key3'}, {key: 'key3'} ]}).then(function () { return db.query(queryFun, {reduce: false, key: 'key2'}); }).then(function (res) { res.rows.should.have.length(1, 'Doc with key'); return db.query(queryFun, {reduce: false, key: 'key3'}); }).then(function (res) { res.rows.should.have.length(2, 'Multiple docs with key'); }); }); }); }); it("Test basic view collation", function () { var values = []; // special values sort before all other types values.push(null); values.push(false); values.push(true); // then numbers values.push(1); values.push(2); values.push(3.0); values.push(4); // then text, case sensitive // currently chrome uses ascii ordering and so wont handle caps properly values.push("a"); //values.push("A"); values.push("aa"); values.push("b"); //values.push("B"); values.push("ba"); values.push("bb"); // then arrays. compared element by element until different. // Longer arrays sort after their prefixes values.push(["a"]); values.push(["b"]); values.push(["b", "c"]); values.push(["b", "c", "a"]); values.push(["b", "d"]); values.push(["b", "d", "e"]); // then object, compares each key value in the list until different. // larger objects sort after their subset objects. values.push({a: 1}); values.push({a: 2}); values.push({b: 1}); values.push({b: 2}); values.push({b: 2, a: 1}); // Member order does matter for collation. // CouchDB preserves member order // but doesn't require that clients will. // (this test might fail if used with a js engine // that doesn't preserve order) values.push({b: 2, c: 2}); return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); } }).then(function (queryFun) { var docs = values.map(function (x, i) { return {_id: (i).toString(), foo: x}; }); return db.bulkDocs({docs: docs}).then(function () { return db.query(queryFun, {reduce: false}); }).then(function (res) { res.rows.forEach(function (x, i) { JSON.stringify(x.key).should.equal(JSON.stringify(values[i]), 'keys collate'); }); return db.query(queryFun, {descending: true, reduce: false}); }).then(function (res) { res.rows.forEach(function (x, i) { JSON.stringify(x.key).should.equal(JSON.stringify( values[values.length - 1 - i]), 'keys collate descending'); }); }); }); }); }); it("Test joins", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { if (doc.doc_id) { emit(doc._id, {_id: doc.doc_id}); } } }).then(function (queryFun) { return db.bulkDocs({docs: [ {_id: 'mydoc', foo: 'bar'}, { doc_id: 'mydoc' } ]}).then(function () { return db.query(queryFun, {include_docs: true, reduce: false}); }).then(function (res) { should.exist(res.rows[0].doc); return res.rows[0].doc._id; }); }).should.become('mydoc', 'mydoc included'); }); }); it("No reduce function", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function () { emit('key', 'val'); } }).then(function (queryFun) { return db.post({foo: 'bar'}).then(function () { return db.query(queryFun); }); }); }).should.be.fulfilled; }); it("Query after db.close", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo, 'val'); } }).then(function (queryFun) { return db.put({_id: 'doc', foo: 'bar'}).then(function () { return db.query(queryFun); }).then(function (res) { res.rows.should.deep.equal([ { id: 'doc', key: 'bar', value: 'val' } ]); return db.close(); }).then(function () { db = new PouchDB(dbName); return db.query(queryFun).then(function (res) { res.rows.should.deep.equal([ { id: 'doc', key: 'bar', value: 'val' } ]); }); }); }); }); }); it("Built in _sum reduce function", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.val, 1); }, reduce: "_sum" }).then(function (queryFun) { return db.bulkDocs({ docs: [ { val: 'bar' }, { val: 'bar' }, { val: 'baz' } ] }).then(function () { return db.query(queryFun, {reduce: true, group_level: 999}); }).then(function (resp) { return resp.rows.map(function (row) { return row.value; }); }); }); }).should.become([2, 1]); }); it("Built in _count reduce function", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.val, doc.val); }, reduce: "_count" }).then(function (queryFun) { return db.bulkDocs({ docs: [ { val: 'bar' }, { val: 'bar' }, { val: 'baz' } ] }).then(function () { return db.query(queryFun, {reduce: true, group_level: 999}); }).then(function (resp) { return resp.rows.map(function (row) { return row.value; }); }); }); }).should.become([2, 1]); }); it("Built in _stats reduce function", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: "function(doc){emit(doc.val, 1);}", reduce: "_stats" }).then(function (queryFun) { return db.bulkDocs({ docs: [ { val: 'bar' }, { val: 'bar' }, { val: 'baz' } ] }).then(function () { return db.query(queryFun, {reduce: true, group_level: 999}); }).then(function (res) { return res.rows[0].value; }); }); }).should.become({ sum: 2, count: 2, min: 1, max: 1, sumsqr: 2 }); }); it.skip("Built in _stats reduce function should throw an error with a promise", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: "function(doc){emit(doc.val, 'lala');}", reduce: "_stats" }).then(function (queryFun) { return db.bulkDocs({ docs: [ { val: 'bar' }, { val: 'bar' }, { val: 'baz' } ] }).then(function () { return db.query(queryFun, {reduce: true, group_level: 999}); }); }); }).should.be.rejected; }); it.skip("Built in _sum reduce function should throw an error with a promise", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: "function(doc){emit(null, doc.val);}", reduce: "_sum" }).then(function (queryFun) { return db.bulkDocs({ docs: [ { val: 1 }, { val: 2 }, { val: 'baz' } ] }).then(function () { return db.query(queryFun, {reduce: true, group: true}); }); }); }).should.be.rejected; }); it.skip("Built in _sum reduce function with num arrays should throw an error", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: "function(doc){emit(null, doc.val);}", reduce: "_sum" }).then(function (queryFun) { return db.bulkDocs({ docs: [ { val: [1, 2, 3] }, { val: 2 }, { val: ['baz']} ] }).then(function () { return db.query(queryFun, {reduce: true, group: true}); }); }); }).should.be.rejected; }); it("Built in _sum can be used with lists of numbers", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: "function(doc){emit(null, doc.val);}", reduce: "_sum" }).then(function (queryFun) { return db.bulkDocs({ docs: [ { _id: '1', val: 2 }, { _id: '2', val: [1, 2, 3, 4] }, { _id: '3', val: [3, 4] }, { _id: '4', val: 1 } ] }).then(function () { return db.query(queryFun, {reduce: true, group: true}); }).then(function (res) { res.should.deep.equal({rows : [{ key : null, value : [7, 6, 3, 4] }]}); }); }); }); }); if (viewType === 'temp') { it("No reduce function, passing just a function", function () { return new PouchDB(dbName).then(function (db) { return db.post({foo: 'bar'}).then(function () { var queryFun = function () { emit('key', 'val'); }; return db.query(queryFun); }); }).should.be.fulfilled; }); } it.skip('Query result should include _conflicts', function () { var db2name = 'test2b' + Math.random(); var cleanup = function () { return new PouchDB(db2name).destroy(); }; var doc1 = {_id: '1', foo: 'bar'}; var doc2 = {_id: '1', foo: 'baz'}; return testUtils.fin(new PouchDB(dbName).then(function (db) { return new PouchDB(db2name).then(function (remote) { var replicate = testUtils.promisify(db.replicate.from, db.replicate); return db.post(doc1).then(function () { return remote.post(doc2); }).then(function () { return replicate(remote); }).then(function () { return db.query(function (doc) { if (doc._conflicts) { emit(doc._conflicts, null); } }, {include_docs : true, conflicts: true}); }).then(function (res) { should.exist(res.rows[0].doc._conflicts); return db.get(res.rows[0].doc._id, {conflicts: true}); }).then(function (res) { should.exist(res._conflicts); }); }); }), cleanup); }); /* jshint maxlen:false */ var icons = [ "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAAC8klEQVQ4y6WTS2hcZQCFv//eO++ZpDMZZjKdZB7kNSUpeWjANikoWiMUtEigBdOFipS6Ercu3bpTKF23uGkWBUGsoBg1KRHapjU0U81rpp3ESdNMZu6dx70zc38XdSFYVz1wNmdxzuKcAy8I8RxNDfs705ne5FmX0+mXUtK0mka2kLvxRC9vAe3nGmRiCQ6reux4auDi6ZenL0wOjaa6uoKK2+kgv1O0l1dvby/8/tvVe1t/XAn6ArvZ3fyzNIBjsQS5YiH6/ul3v/z0/AcfTx8fC24+zgvV4SXccYTtYlGM9MSDMydee1W27OQPd5d+Hujure4bZRQVeLCTY2p44tJ7M2/Pjg1lOLQkXy2scP3OQ1b3Snzx3SK/PCoxOphh7q13ZqeGJy492MmhAkoyHMUlRN8b4yfnBnqSWLqJItzkXZPoWhzF4WZdjGJ6+7H0OoPxFG9OnppzCtGXCEdRZ16axu1yffjRmfPnYqEw7WIdj1OlO6wx1e0g7hckO1ReH4wSrkgUVcEfDITub6w9Gus7tqS4NAcOVfMpCFq2jdrjwxv2cG48SejPFe59/gmnyuuMHA0ien0oR1x0BgJ4XG5fwO9Hk802sm3TbFiYVhNNU1FUBYCBsRNEmiad469gYyNUgRDPipNIQKKVajo1s1F9WjqgVjZQELg9Ek3TUFNHCaXnEEiQEvkPDw4PqTfMalk3UKt1g81ioRgLRc6MxPtDbdtGKgIhBdgSKW2kLWm327SaLayGxfzCzY2vf/zms0pVLyn7lQOadbmxuHb7WrawhW220J+WKZXK6EaNsl7F0GsYep1q3eTW6grfLv90zZRyI7dfRDNtSPdE+av05PL8re+HgdlMPI2wJXrDRAACgdVusfZ4k+uLN+eXs/cvp7oitP895UQogt6oxYZiiYsnMxMXpjPjqaC/QwEoGRX71+yd7aXs3asPd/NXAm7vbv5g7//P1OHxpvsj8bMep8sPULdMY32vcKNSr/3nTC+MvwEdhUhhkKTyPgAAAEJ0RVh0Y29tbWVudABGaWxlIHNvdXJjZTogaHR0cDovL3d3dy5zc2J3aWtpLmNvbS9GaWxlOktpcmJ5SGVhZFNTQkIucG5nSbA1rwAAACV0RVh0Y3JlYXRlLWRhdGUAMjAxMC0xMi0xNFQxNjozNDoxMCswMDowMDpPBjcAAAAldEVYdG1vZGlmeS1kYXRlADIwMTAtMTAtMDdUMjA6NTA6MzYrMDA6MDCjC6s7AAAAAElFTkSuQmCC", "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC3ElEQVQ4jX2SX2xTdRzFP/d3f5d7u7ZbGes6LyAFWSiNmbMuSqb4wgxGVMiYT/BkNPMNfV1MDAFfNDHxwWSJU4wsMsKLEhI3gmE0JHO6FTBzMrZlS3V3Qun+sG70tvePD4ZlI8BJvi/fc/LN9+QceAIanm1oa2xo7HuSRn0c0dUq5fbd2teerLRHxqzuhzjDEs+0VYSrT4vHHbAW1ZrWg9aeYweurdv3vCsTL7Yy+GmHfcb3/Qn5T49MCYMW85Dz2Vphdl6jWPLJjmAOfSN/QsFY+ZdfNic5tuUFzLEfZjOLi1Xt5C7J44VJ6V/9Up546M0NFz/Xhp070l8789elf65DH3wvFYoACK2KNiMMz79Nx9ojEZOWP/Lx1NCv/7v8fTDK0fe34QF/ZsS5rkxhAUC4ZZJeGfQgovFNPu4+KtsAYsWad+rjM1TqHvcsqNmUY59pow/HqI07b62msEtqwijzku4inXmorqXllWpxybgb3f/akVLi7lAJ60KA+gMOTTcSWKc1rgZyi1f+8joB1PPDbn85W/GzYxOL1XgJaRDoTW9ID8ysnKyK24dSh/3auoSGUuGQFxb2UzlERL19Nu12AkiArkwhA6HDT29yLi+j1s3Oih/royUZjXihYg5W7txH5EGrhI17wMy6yWRUT47m7NHVHmypcirnl8SO6pBnNiWdr4q6+kZksxI3oiDCsLwE9/LARlguIm/lXbmuif3TTjG4Ejj724RbDuleezimbHv1dW/rrTQE62ByRLC8AJ4C2SkIIiauTbsD65rYlSlYp9LlTy5muBkx/WYZgMQ++HtcsGunR33S5+Y4NKcgHFQAeGSV09PsnZtRuu05uD8LZsDDXgDXhubd0DfAaM9l7/t1FtbC871Sbk5MbdX5oHwbOs+ovVPj9C7N0VhyUfv61Q/7x0qDqyk8CnURZcdkzufbC0p7bVn77otModRkGqdefs79qOj7xgPdf3d0KpBuuY7dAAAAAElFTkSuQmCC", "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwMS8wNy8wOCumXF8AAAAfdEVYdFNvZnR3YXJlAE1hY3JvbWVkaWEgRmlyZXdvcmtzIDi1aNJ4AAADHElEQVQ4EYXBe0wUBADH8R/CcSccQnfcIcbrXgRixKPSMIxklU4tJOUfyflIcmVJzamTVjJrJIRa6OZ4DmGMwSoEfKIVkcTC5qNRmqxpuki3VFiIjMc33fijka3PR/o3s7/R+Hl8QTgpxz2kHHWTuC8Cf7PxlCSr/ke0Ndrc5ioPJejONHxHjfiOGAkYNuNqDMX2WEC3pCf0H2LMScbLMcciiB0KJGbcwMy7RmYOG4kdMxA7EkBsRySB6X43JM3TJD6aoT3OvOlsPxVNX+807oyJ/rtiYFgMI271mdjdEcMjhQ8jl1eNpEDdV/PugrajpZu/ejndwafvpdB/1sHtS+EM/m4BBGNTuNCawPk2B6M3jNRXRvJSmpOG4je7Gj5Yekw7spLPXe8s42xdMfXvuzh3OIHerihADP1poeuQP0f2vMbX5fmcbnHS3eDg+6oCbp+ppWjV3Iu6Lzf10fzGotnUFVmp2pBGX3sS54+7KXsribq8V/nrl2aun66gfOOLnKx0cqLqKTalP14iyaQJ7uwsH/p7oli/OJV31q7i7bREmovfYPBSE83FG1m37BVWL17I1W8cbMn1RdIz+ofpCdHBtcvnhIxXf5zLjjLI23qQ4StNjF5rpSi/ltyd0FK9k8xk23hqQuhBSW49QGlOZjwdpZ8w2NsDV9vh8klGfvuJzuoytq6cjTTlM0l+msT0kMu6u/Bw3uBHza+zaJmFwsol7G3MoaRxHbtqMslcYWNb1Qr2dxYMRSSFV0iyaoItLjrizIUf6znRuZ/EjCie3+5iXomTZw+EMb82jNQSB8996CYxI5za5gKuXDvE00/O6pXk0T3BnoiQ75r2bSNnw3JU5sWc9iCy17j441cTQzcN5Kx3kdpqxesLsXTtCxwpzyc5ztEjyaUJBkmrJR0wxHtjrQjC+XMIK2/5kjPgg/uiHXuDBUOKN5JaJK2RFKhJkrItQTe7Z8SRNTUMc6QBebx+kMfrW98obxaZQ+mwz2KTLXhA0hI9gGuuv3/TZruNDL9grDKVS5qqe8wyFC00Wdlit7MgIOBLSYma8DfYI5E1lrjnEQAAAABJRU5ErkJggg==", "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB1klEQVR42n2TzytEURTHv3e8N1joRhZGzJsoCjsLhcw0jClKWbHwY2GnLGUlIfIP2IjyY2djZTHSMJNQSilFNkz24z0/Ms2MrnvfvMu8mcfZvPvuPfdzz/mecwgKLNYKb0cFEgXbRvwV2s2HuWazCbzKA5LvNecDXayBjv9NL7tEpSNgbYzQ5kZmAlSXgsGGXmS+MjhKxDHgC+quyaPKQtoPYMQPOh5U9H6tBxF+Icy/aolqAqLP5wjWd5r/Ip3YXVILrF4ZRYAxDhCOJ/yCwiMI+/xgjOEzmzIhAio04GeGayIXjQ0wGoAuQ5cmIjh8jNo0GF78QwNhpyvV1O9tdxSSR6PLl51FnIK3uQ4JJQME4sCxCIRxQbMwPNSjqaobsfskm9l4Ky6jvCzWEnDKU1ayQPe5BbN64vYJ2vwO7CIeLIi3ciYAoby0M4oNYBrXgdgAbC/MhGCRhyhCZwrcEz1Ib3KKO7f+2I4iFvoVmIxHigGiZHhPIb0bL1bQApFS9U/AC0ulSXrrhMotka/lQy0Ic08FDeIiAmDvA2HX01W05TopS2j2/H4T6FBVbj4YgV5+AecyLk+CtvmsQWK8WZZ+Hdf7QGu7fobMuZHyq1DoJLvUqQrfM966EU/qYGwAAAAASUVORK5CYII=", "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAEG0lEQVQ4EQEQBO/7AQAAAAAAAAAAAAAAAAAAAACmm0ohDxD8bwT//ksOBPAhAAAAAPL8EN8IDQLB5eQEhVpltt8AAAAAAAAAAAAAAAABAAAAAAAAAACHf0UGKSgBgygY7m/w4O8F5t71ABMaCQAPEAQAAAAAAPwEBgAMFAn74/ISnunoA3RcZ7f2AAAAAAEAAAAAh39FBjo4AZYTAOtf1sLmAvb1+gAAAAAALzsVACEn+wAAAAAA/f4G/+LcAgH9AQIA+hAZpuDfBmhaZrb1AwAAAABtaCSGHAjraf///wD47/kB9vX7AAAAAAAYHgsAERT+AAAAAAACAf0BERT/AAQHB/746/IuBRIMFfL3G8ECpppKHigY7m/68vcCHRv0AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//0ADgvzAgP//gAWBe1hUEgMOgIKDfxr9Oz3BRsiAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHCP///zu8gMjIftYAgkD/1ID//4ABwb6Af//AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBPwBAAAAAAP0710CDgTvIQD//QAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//QD8BAYADQv//gQAAAAAAAAAAAAAAgABAf4AAAAAAAAAAAAAAAAAAAAAAAABAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//gAAAAAABPL7D+D57Owh0MQAAAAAAAD08/sAAAAAAAAAAADj2fQA8ewGAAAAAAAAAAAAAAAAAAAAAAAAAAAA+/r1AAwECwIEAggDugsNBGcAAAAAAwMBAO7o+AAAAAAAAAAAAAgKBAAOEAUAAAAAAAAAAAAAAAAAAAAAAAAAAADz8vwA/QwRowTr6gSLHSQQYvfr9QUhJ/sA6OEEAPPy+QAAAAAAFR0IACEn+wAAAAAAAAAAAAAAAAAAAAAA4+YP/g0OAgDT3wWoAlpltt/d7BKYBAwH/uTmDf4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPL1Df798fUC+AgSqMfL9sICAAAAAOblAHXzBRSo////APTz+wD//wAAAAAAAAAAAAAAAAAAAAEBAP3+Bv/j5g/+7uL3AukDH97g3wZomJzA9wMAAAAAs7jd/kE8J7n9BwoSJSgGMQYD/wL++/8ABAUCAPb1BQDw7AIA8e8DAQAFBf/0DBqj6OgGTlpmtvUAAAAAAQAAAAAAAAAAAAAAAFFRPg1SSAwbGxv8cQn67mMHBf7/AwL/APb5AwH/DRCn294GpMLH9sKdoMD3AAAAAAAAAABEawlCEphz4AAAAABJRU5ErkJggg==" ]; /* jshint maxlen:100 */ var iconDigests = [ "md5-Mf8m9ehZnCXC717bPkqkCA==", "md5-fdEZBYtnvr+nozYVDzzxpA==", "md5-ImDARszfC+GA3Cv9TVW4HA==", "md5-hBsgoz3ujHM4ioa72btwow==", "md5-jDUyV6ySnTVANn2qq3332g==" ]; var iconLengths = [1047, 789, 967, 527, 1108]; it('#190 Query works with attachments=true', function () { var db = new PouchDB(dbName); var docs = []; for (var i = 0; i < 5; i++) { docs.push({ _id: i.toString(), _attachments: { 'foo.png': { data: icons[i], content_type: 'image/png' } } }); } return db.bulkDocs(docs).then(function () { return createView(db, { map: function (doc) { emit(doc._id); } }); }).then(function (queryFun) { return db.query(queryFun, { include_docs: true, attachments: true }).then(function (res) { var attachments = res.rows.map(function (row) { var doc = row.doc; delete doc._attachments['foo.png'].revpos; return doc._attachments; }); attachments.should.deep.equal(icons.map(function (icon, i) { return { "foo.png": { "content_type": "image/png", "data": icon, "digest": iconDigests[i] } }; }), 'works with attachments=true'); return db.query(queryFun, {include_docs: true}); }).then(function (res) { var attachments = res.rows.map(function (row) { var doc = row.doc; delete doc._attachments['foo.png'].revpos; return doc._attachments['foo.png']; }); attachments.should.deep.equal(icons.map(function (icon, i) { return { "content_type": "image/png", stub: true, "digest": iconDigests[i], length: iconLengths[i] }; }), 'works with attachments=false'); return db.query(queryFun, {attachments: true}); }).then(function (res) { res.rows.should.have.length(5); res.rows.forEach(function (row) { should.not.exist(row.doc, 'ignored if include_docs=false'); }); }); }); }); it('#2858 Query works with attachments=true, binary=true 1', function () { // Need to avoid the cache to workaround // https://issues.apache.org/jira/browse/COUCHDB-2880 var db = new PouchDB(dbName, {ajax: {cache: false}}); var docs = []; for (var i = 0; i < 5; i++) { docs.push({ _id: i.toString(), _attachments: { 'foo.png': { data: icons[i], content_type: 'image/png' } } }); } return db.bulkDocs(docs).then(function () { return createView(db, { map: function (doc) { emit(doc._id); } }); }).then(function (queryFun) { return db.query(queryFun, { include_docs: true, attachments: true, binary: true }).then(function (res) { res.rows.forEach(function (row) { var doc = row.doc; Object.keys(doc._attachments).forEach(function (attName) { var att = doc._attachments[attName]; should.not.exist(att.stub); att.data.should.not.be.a('string'); }); }); }); }); }); it('#2858 Query works with attachments=true, binary=true 2', function () { // Need to avoid the cache to workaround // https://issues.apache.org/jira/browse/COUCHDB-2880 var db = new PouchDB(dbName, {ajax: {cache: false}}); var docs = []; for (var i = 0; i < 5; i++) { docs.push({ _id: i.toString() }); } return db.bulkDocs(docs).then(function () { return createView(db, { map: function (doc) { emit(doc._id); } }); }).then(function (queryFun) { return db.query(queryFun, { include_docs: true, attachments: true, binary: true }).then(function (res) { res.rows.forEach(function (row) { var doc = row.doc; should.not.exist(doc._attachments); }); }); }); }); it('#242 conflicts at the root level', function () { var db = new PouchDB(dbName); return db.bulkDocs([ { foo: '1', _id: 'foo', _rev: '1-w', _revisions: {start: 1, ids: ['w']} } ], {new_edits: false}).then(function () { return createView(db, { map: function (doc) { emit(doc.foo); } }).then(function (queryFun) { return db.query(queryFun).then(function (res) { res.rows[0].key.should.equal('1'); return db.bulkDocs([ { foo: '2', _id: 'foo', _rev: '1-x', _revisions: {start: 1, ids: ['x']} } ], {new_edits: false}).then(function () { return db.query(queryFun); }).then(function (res) { res.rows[0].key.should.equal('2'); return db.bulkDocs([ { foo: '3', _id: 'foo', _rev: '1-y', _deleted: true, _revisions: {start: 1, ids: ['y']} } ], {new_edits: false}); }).then(function () { return db.query(queryFun); }).then(function (res) { res.rows[0].key.should.equal('2'); }); }); }); }); }); it('#242 conflicts at the root+1 level', function () { var db = new PouchDB(dbName); return db.bulkDocs([ { foo: '2', _id: 'foo', _rev: '1-x', _revisions: {start: 1, ids: ['x']} }, { foo: '3', _id: 'foo', _rev: '2-y', _deleted: true, _revisions: {start: 2, ids: ['y', 'x']} } ], {new_edits: false}).then(function () { return createView(db, { map: function (doc) { emit(doc.foo); } }).then(function (queryFun) { return db.query(queryFun).then(function (res) { res.rows.length.should.equal(0); return db.bulkDocs([ { foo: '1', _id: 'foo', _rev: '1-w', _revisions: {start: 1, ids: ['w']} } ], {new_edits: false}).then(function () { return db.query(queryFun); }).then(function (res) { res.rows[0].key.should.equal('1'); return db.bulkDocs([ { foo: '4', _id: 'foo', _rev: '1-z', _revisions: {start: 1, ids: ['z']} } ], {new_edits: false}); }).then(function () { return db.query(queryFun); }).then(function (res) { res.rows[0].key.should.equal('4'); }); }); }); }); }); it('Views should include _conflicts', function () { var db2name = 'test2' + Math.random(); var cleanup = function () { return new PouchDB(db2name).destroy(); }; var doc1 = {_id: '1', foo: 'bar'}; var doc2 = {_id: '1', foo: 'baz'}; return testUtils.fin(new PouchDB(dbName).then(function (db) { return new PouchDB(db2name).then(function (remote) { return createView(db, { map : function (doc) { emit(doc._id, !!doc._conflicts); } }).then(function (queryFun) { var replicate = testUtils.promisify(db.replicate.from, db.replicate); return db.post(doc1).then(function () { return remote.post(doc2); }).then(function () { return replicate(remote); }).then(function () { return db.get(doc1._id, {conflicts: true}); }).then(function (res) { should.exist(res._conflicts); return db.query(queryFun); }).then(function (res) { res.rows[0].value.should.equal(true); }); }); }); }), cleanup); }); it("Test view querying with limit option", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { if (doc.foo === 'bar') { emit(doc.foo); } } }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: 'bar' }, { foo: 'bar' }, { foo: 'baz' } ] }).then(function () { return db.query(queryFun, { limit: 1 }); }).then(function (res) { res.total_rows.should.equal(2, 'Correctly returns total rows'); res.rows.should.have.length(1, 'Correctly limits returned rows'); }); }); }); }); it("Test view querying with custom reduce function", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); }, reduce: function(keys) { return keys.map(function(keyId) { var key = keyId[0]; // var id = keyId[1]; return key.join(''); }); } }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: ['foo', 'bar'] }, { foo: ['foo', 'bar'] }, { foo: ['foo', 'bar', 'baz'] }, { foo: ['baz'] }, { foo: ['baz', 'bar'] } ] }).then(function () { return db.query(queryFun, { reduce: true }); }).then(function (res) { res.rows.should.have.length(1, 'Correctly reduced returned rows'); should.not.exist(res.rows[0].key, 'Correct, non-existing key'); res.rows[0].value.should.have.length(5); res.rows[0].value.should.include('foobarbaz'); res.rows[0].value.should.include('foobar'); // twice res.rows[0].value.should.include('bazbar'); res.rows[0].value.should.include('baz'); return db.query(queryFun, { group_level: 1, reduce: true }); }).then(function (res) { res.rows.should.have.length(2, 'Correctly group reduced rows'); res.rows[0].key.should.deep.equal(['baz']); res.rows[0].value.should.have.length(2); res.rows[0].value.should.include('bazbar'); res.rows[0].value.should.include('baz'); res.rows[1].key.should.deep.equal(['foo']); res.rows[1].value.should.have.length(3); res.rows[1].value.should.include('foobarbaz'); res.rows[1].value.should.include('foobar'); // twice }); }); }); }); it("Test view querying with group_level option and reduce", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); }, reduce: '_count' }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: ['foo', 'bar'] }, { foo: ['foo', 'bar'] }, { foo: ['foo', 'bar', 'baz'] }, { foo: ['baz'] }, { foo: ['baz', 'bar'] } ] }).then(function () { return db.query(queryFun, { group_level: 1, reduce: true}); }).then(function (res) { res.rows.should.have.length(2, 'Correctly group returned rows'); res.rows[0].key.should.deep.equal(['baz']); res.rows[0].value.should.equal(2); res.rows[1].key.should.deep.equal(['foo']); res.rows[1].value.should.equal(3); return db.query(queryFun, { group_level: 999, reduce: true}); }).then(function (res) { res.rows.should.have.length(4, 'Correctly group returned rows'); res.rows[2].key.should.deep.equal(['foo', 'bar']); res.rows[2].value.should.equal(2); return db.query(queryFun, { group_level: '999', reduce: true}); }).then(function (res) { res.rows.should.have.length(4, 'Correctly group returned rows'); res.rows[2].key.should.deep.equal(['foo', 'bar']); res.rows[2].value.should.equal(2); return db.query(queryFun, { group_level: 0, reduce: true}); }).then(function (res) { res.rows.should.have.length(1, 'Correctly group returned rows'); res.rows[0].value.should.equal(5); }); }); }); }); it("Test view querying with invalid group_level options", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); }, reduce: '_count' }).then(function (queryFun) { return db.query(queryFun, { group_level: -1, reduce: true }).then(function (res) { res.should.not.exist('expected error on invalid group_level'); }).catch(function (err) { err.status.should.equal(400); err.message.should.be.a('string'); return db.query(queryFun, { group_level: 'exact', reduce: true}); }).then(function (res) { res.should.not.exist('expected error on invalid group_level'); }).catch(function (err) { err.status.should.equal(400); err.message.should.be.a('string'); }); }); }); }); it("Test view querying with limit option and reduce", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); }, reduce: '_count' }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: 'bar' }, { foo: 'bar' }, { foo: 'baz' } ] }).then(function () { return db.query(queryFun, { limit: 1, group: true, reduce: true}); }).then(function (res) { res.rows.should.have.length(1, 'Correctly limits returned rows'); res.rows[0].key.should.equal('bar'); res.rows[0].value.should.equal(2); }).then(function () { return db.query(queryFun, { limit: '1', group: true, reduce: true}); }).then(function (res) { res.rows.should.have.length(1, 'Correctly limits returned rows'); res.rows[0].key.should.equal('bar'); res.rows[0].value.should.equal(2); }); }); }); }); it("Test view querying with invalid limit option and reduce", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); }, reduce: '_count' }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: 'bar' }, { foo: 'bar' }, { foo: 'baz' } ] }).then(function () { return db.query(queryFun, { limit: -1, group: true, reduce: true}); }).then(function (res) { res.should.not.exist('expected error on invalid group_level'); }).catch(function (err) { err.status.should.equal(400); err.message.should.be.a('string'); return db.query(queryFun, { limit: '1a', group: true, reduce: true}); }).then(function (res) { res.should.not.exist('expected error on invalid group_level'); }).catch(function (err) { err.status.should.equal(400); err.message.should.be.a('string'); }); }); }); }); it('Test unsafe object usage (#244)', function () { var db = new PouchDB(dbName); return db.bulkDocs([ {_id: 'constructor'} ]).then(function (res) { var rev = res[0].rev; return createView(db, { map: function (doc) { emit(doc._id); } }).then(function (queryFun) { return db.query(queryFun, {include_docs: true}).then(function (res) { res.rows.should.deep.equal([ { "key": "constructor", "id": "constructor", "value": null, "doc": { "_id": "constructor", "_rev": rev } } ]); return db.bulkDocs([ {_id: 'constructor', _rev: rev} ]); }).then(function (res) { rev = res[0].rev; return db.query(queryFun, {include_docs: true}); }).then(function (res) { res.rows.should.deep.equal([ { "key": "constructor", "id": "constructor", "value": null, "doc": { "_id": "constructor", "_rev": rev } } ]); return db.bulkDocs([ {_id: 'constructor', _rev: rev, _deleted: true} ]); }).then(function (res) { rev = res[0].rev; return db.query(queryFun, {include_docs: true}); }).then(function (res) { res.rows.should.deep.equal([]); }); }); }); }); it("Test view querying with a skip option and reduce", function () { var qf; return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); }, reduce: '_count' }).then(function (queryFun) { qf = queryFun; return db.bulkDocs({ docs: [ { foo: 'bar' }, { foo: 'bar' }, { foo: 'baz' } ] }).then(function () { return db.query(queryFun, {skip: 1, group: true, reduce: true}); }); }).then(function (res) { res.rows.should.have.length(1, 'Correctly limits returned rows'); res.rows[0].key.should.equal('baz'); res.rows[0].value.should.equal(1); }).then(function () { return db.query(qf, {skip: '1', group: true, reduce: true}); }).then(function (res) { res.rows.should.have.length(1, 'Correctly limits returned rows'); res.rows[0].key.should.equal('baz'); res.rows[0].value.should.equal(1); }); }); }); it("Test view querying with invalid skip option and reduce", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); }, reduce: '_count' }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: 'bar' }, { foo: 'bar' }, { foo: 'baz' } ] }).then(function () { return db.query(queryFun, { skip: -1, group: true, reduce: true}); }).then(function (res) { res.should.not.exist('expected error on invalid group_level'); }).catch(function (err) { err.status.should.equal(400); err.message.should.be.a('string'); return db.query(queryFun, { skip: '1a', group: true, reduce: true}); }).then(function (res) { res.should.not.exist('expected error on invalid group_level'); }).catch(function (err) { err.status.should.equal(400); err.message.should.be.a('string'); }); }); }); }); it("Special document member _doc_id_rev should never leak outside", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { if (doc.foo === 'bar') { emit(doc.foo); } } }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: 'bar' } ] }).then(function () { return db.query(queryFun, { include_docs: true }); }).then(function (res) { should.not.exist(res.rows[0].doc._doc_id_rev, '_doc_id_rev is leaking but should not'); }); }); }); }); it.skip('multiple view creations and cleanups', function () { return new PouchDB(dbName).then(function (db) { var map = function (doc) { emit(doc.num); }; function createView(name) { var storableViewObj = { map: map.toString() }; return db.put({ _id: '_design/' + name, views: { theView: storableViewObj } }); } return db.bulkDocs({ docs: [ {_id: 'test1'} ] }).then(function () { function sequence(name) { return createView(name).then(function () { return db.query(name + '/theView').then(function () { return db.viewCleanup(); }); }); } var attempts = []; var numAttempts = 10; for (var i = 0; i < numAttempts; i++) { attempts.push(sequence('test' + i)); } return Promise.all(attempts).then(function () { var keys = []; for (var i = 0; i < numAttempts; i++) { keys.push('_design/test' + i); } return db.allDocs({keys : keys, include_docs : true}); }).then(function (res) { var docs = res.rows.map(function (row) { row.doc._deleted = true; return row.doc; }); return db.bulkDocs({docs : docs}); }).then(function () { return db.viewCleanup(); }).then(function (res) { res.ok.should.equal(true); }); }); }); }); it('If reduce function returns 0, resulting value should not be null', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo); }, reduce: function () { return 0; } }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: 'bar' } ] }).then(function () { return db.query(queryFun).then(function (data) { should.exist(data.rows[0].value); }); }); }); }); }); it('Testing skip with a view', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.foo); } }).then(function (queryFun) { return db.bulkDocs({ docs: [ { foo: 'bar' }, { foo: 'baz' }, { foo: 'baf' } ] }).then(function () { return db.query(queryFun, {skip: 1}); }).then(function (data) { data.rows.should.have.length(2); data.offset.should.equal(1); data.total_rows.should.equal(3); }); }); }); }); it('Map documents on 0/null/undefined/empty string', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.num); } }).then(function (mapFunction) { var docs = [ {_id: '0', num: 0}, {_id: '1', num: 1}, {_id: 'undef' /* num is undefined */}, {_id: 'null', num: null}, {_id: 'empty', num: ''}, {_id: 'nan', num: NaN}, {_id: 'inf', num: Infinity}, {_id: 'neginf', num: -Infinity} ]; return db.bulkDocs({docs: docs}).then(function () { return db.query(mapFunction, {key: 0}); }).then(function (data) { data.rows.should.have.length(1); data.rows[0].id.should.equal('0'); return db.query(mapFunction, {key: ''}); }).then(function (data) { data.rows.should.have.length(1); data.rows[0].id.should.equal('empty'); return db.query(mapFunction, {key: undefined}); }).then(function (data) { data.rows.should.have.length(8); // everything // keys that should all resolve to null var emptyKeys = [null, NaN, Infinity, -Infinity]; return Promise.all(emptyKeys.map(function (emptyKey) { return db.query(mapFunction, {key: emptyKey}).then(function (data) { data.rows.map(function (row) { return row.id; }).should.deep.equal(['inf', 'nan', 'neginf', 'null', 'undef']); }); })); }); }); }); }); it('Testing query with keys', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.field); } }).then(function (queryFun) { var opts = {include_docs: true}; return db.bulkDocs({ docs: [ {_id: 'doc_0', field: 0}, {_id: 'doc_1', field: 1}, {_id: 'doc_2', field: 2}, {_id: 'doc_empty', field: ''}, {_id: 'doc_null', field: null}, {_id: 'doc_undefined' /* field undefined */}, {_id: 'doc_foo', field: 'foo'} ] }).then(function () { return db.query(queryFun, opts); }).then(function (data) { data.rows.should.have.length(7, 'returns all docs'); opts.keys = []; return db.query(queryFun, opts); }).then(function (data) { data.rows.should.have.length(0, 'returns 0 docs'); opts.keys = [0]; return db.query(queryFun, opts); }).then(function (data) { data.rows.should.have.length(1, 'returns one doc'); data.rows[0].doc._id.should.equal('doc_0'); opts.keys = [2, 'foo', 1, 0, null, '']; return db.query(queryFun, opts); }).then(function (data) { // check that the returned ordering fits opts.keys data.rows.should.have.length(7, 'returns 7 docs in correct order'); data.rows[0].doc._id.should.equal('doc_2'); data.rows[1].doc._id.should.equal('doc_foo'); data.rows[2].doc._id.should.equal('doc_1'); data.rows[3].doc._id.should.equal('doc_0'); data.rows[4].doc._id.should.equal('doc_null'); data.rows[5].doc._id.should.equal('doc_undefined'); data.rows[6].doc._id.should.equal('doc_empty'); opts.keys = [3, 1, 4, 2]; return db.query(queryFun, opts); }).then(function (data) { // nonexistent keys just give us holes in the list data.rows.should.have.length(2, 'returns 2 non-empty docs'); data.rows[0].key.should.equal(1); data.rows[0].doc._id.should.equal('doc_1'); data.rows[1].key.should.equal(2); data.rows[1].doc._id.should.equal('doc_2'); opts.keys = [2, 1, 2, 0, 2, 1]; return db.query(queryFun, opts); }).then(function (data) { // with duplicates, we return multiple docs data.rows.should.have.length(6, 'returns 6 docs with duplicates'); data.rows[0].doc._id.should.equal('doc_2'); data.rows[1].doc._id.should.equal('doc_1'); data.rows[2].doc._id.should.equal('doc_2'); data.rows[3].doc._id.should.equal('doc_0'); data.rows[4].doc._id.should.equal('doc_2'); data.rows[5].doc._id.should.equal('doc_1'); opts.keys = [2, 1, 2, 3, 2]; return db.query(queryFun, opts); }).then(function (data) { // duplicates and unknowns at the same time, for maximum weirdness data.rows.should.have.length(4, 'returns 2 docs with duplicates/unknowns'); data.rows[0].doc._id.should.equal('doc_2'); data.rows[1].doc._id.should.equal('doc_1'); data.rows[2].doc._id.should.equal('doc_2'); data.rows[3].doc._id.should.equal('doc_2'); opts.keys = [3]; return db.query(queryFun, opts); }).then(function (data) { data.rows.should.have.length(0, 'returns 0 doc due to unknown key'); opts.include_docs = false; opts.keys = [3, 2]; return db.query(queryFun, opts); }).then(function (data) { data.rows.should.have.length(1, 'returns 1 doc due to unknown key'); data.rows[0].id.should.equal('doc_2'); should.not.exist(data.rows[0].doc, 'no doc, since include_docs=false'); }); }); }); }); it('Testing query with multiple keys, multiple docs', function () { function ids(row) { return row.id; } var opts = {keys: [0, 1, 2]}; var spec; return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.field1); emit(doc.field2); } }).then(function (mapFunction) { return db.bulkDocs({ docs: [ {_id: '0', field1: 0}, {_id: '1a', field1: 1}, {_id: '1b', field1: 1}, {_id: '1c', field1: 1}, {_id: '2+3', field1: 2, field2: 3}, {_id: '4+5', field1: 4, field2: 5}, {_id: '3+5', field1: 3, field2: 5}, {_id: '3+4', field1: 3, field2: 4} ] }).then(function () { spec = ['0', '1a', '1b', '1c', '2+3']; return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); opts.keys = [3, 5, 4, 3]; spec = ['2+3', '3+4', '3+5', '3+5', '4+5', '3+4', '4+5', '2+3', '3+4', '3+5']; return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); }); }); }); }); it('Testing multiple emissions (issue #14)', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.foo); emit(doc.bar); emit(doc.foo); emit(doc.bar, 'multiple values!'); emit(doc.bar, 'crayon!'); } }).then(function (mapFunction) { return db.bulkDocs({ docs: [ {_id: 'doc1', foo : 'foo', bar : 'bar'}, {_id: 'doc2', foo : 'foo', bar : 'bar'} ] }).then(function () { var opts = {keys: ['foo', 'bar']}; return db.query(mapFunction, opts); }); }).then(function (data) { data.rows.should.have.length(10); data.rows[0].key.should.equal('foo'); data.rows[0].id.should.equal('doc1'); data.rows[1].key.should.equal('foo'); data.rows[1].id.should.equal('doc1'); data.rows[2].key.should.equal('foo'); data.rows[2].id.should.equal('doc2'); data.rows[3].key.should.equal('foo'); data.rows[3].id.should.equal('doc2'); data.rows[4].key.should.equal('bar'); data.rows[4].id.should.equal('doc1'); should.not.exist(data.rows[4].value); data.rows[5].key.should.equal('bar'); data.rows[5].id.should.equal('doc1'); data.rows[5].value.should.equal('crayon!'); data.rows[6].key.should.equal('bar'); data.rows[6].id.should.equal('doc1'); data.rows[6].value.should.equal('multiple values!'); data.rows[7].key.should.equal('bar'); data.rows[7].id.should.equal('doc2'); should.not.exist(data.rows[7].value); data.rows[8].key.should.equal('bar'); data.rows[8].id.should.equal('doc2'); data.rows[8].value.should.equal('crayon!'); data.rows[9].key.should.equal('bar'); data.rows[9].id.should.equal('doc2'); data.rows[9].value.should.equal('multiple values!'); }); }); }); it('Testing multiple emissions (complex keys)', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function () { emit(['a'], 1); emit(['b'], 3); emit(['a'], 2); } }).then(function (mapFunction) { return db.bulkDocs({ docs: [ {_id: 'doc1', foo: 'foo', bar: 'bar'} ] }).then(function () { return db.query(mapFunction); }); }).then(function (data) { data.rows.should.have.length(3); data.rows[0].key.should.eql(['a']); data.rows[0].value.should.equal(1); data.rows[1].key.should.eql(['a']); data.rows[1].value.should.equal(2); data.rows[2].key.should.eql(['b']); data.rows[2].value.should.equal(3); }); }); }); it('Testing empty startkeys and endkeys', function () { var opts = {startkey: null, endkey: ''}; function ids(row) { return row.id; } var spec; return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.field); } }).then(function (mapFunction) { return db.bulkDocs({ docs: [ {_id: 'doc_empty', field: ''}, {_id: 'doc_null', field: null}, {_id: 'doc_undefined' /* field undefined */}, {_id: 'doc_foo', field: 'foo'} ] }).then(function () { spec = ['doc_null', 'doc_undefined', 'doc_empty']; return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); opts = {startkey: '', endkey: 'foo'}; spec = ['doc_empty', 'doc_foo']; return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); opts = {startkey: null, endkey: null}; spec = ['doc_null', 'doc_undefined']; return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); opts.descending = true; spec.reverse(); return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); }); }); }); }); it('#238 later non-winning revisions', function () { var db = new PouchDB(dbName); return createView(db, { map: function (doc) { emit(doc.name); } }).then(function (mapFun) { return db.bulkDocs([{ _id: 'doc', name: 'zoot', _rev: '2-x', _revisions: { start: 2, ids: ['x', 'y'] } }], {new_edits: false}).then(function () { return db.query(mapFun); }).then(function (res) { res.rows.should.have.length(1); res.rows[0].id.should.equal('doc'); res.rows[0].key.should.equal('zoot'); return db.bulkDocs([{ _id: 'doc', name: 'suit', _rev: '2-w', _revisions: { start: 2, ids: ['w', 'y'] } }], {new_edits: false}); }).then(function () { return db.query(mapFun); }).then(function (res) { res.rows.should.have.length(1); res.rows[0].id.should.equal('doc'); res.rows[0].key.should.equal('zoot'); }); }); }); it('#238 later non-winning deleted revisions', function () { var db = new PouchDB(dbName); return createView(db, { map: function (doc) { emit(doc.name); } }).then(function (mapFun) { return db.bulkDocs([{ _id: 'doc', name: 'zoot', _rev: '2-x', _revisions: { start: 2, ids: ['x', 'y'] } }], {new_edits: false}).then(function () { return db.query(mapFun); }).then(function (res) { res.rows.should.have.length(1); res.rows[0].id.should.equal('doc'); res.rows[0].key.should.equal('zoot'); return db.bulkDocs([{ _id: 'doc', name: 'suit', _deleted: true, _rev: '2-z', _revisions: { start: 2, ids: ['z', 'y'] } }], {new_edits: false}); }).then(function () { return db.query(mapFun); }).then(function (res) { res.rows.should.have.length(1); res.rows[0].id.should.equal('doc'); res.rows[0].key.should.equal('zoot'); }); }); }); it('#238 query with conflicts', function () { var db = new PouchDB(dbName); return createView(db, { map: function (doc) { emit(doc.name); } }).then(function (mapFun) { return db.bulkDocs([ { _id: 'doc', name: 'zab', _rev: '2-y', _revisions: { start: 1, ids: ['y'] } }, { _id: 'doc', name: 'zoot', _rev: '2-x', _revisions: { start: 2, ids: ['x', 'y'] } } ], {new_edits: false}).then(function () { return db.query(mapFun); }).then(function (res) { res.rows.should.have.length(1); res.rows[0].id.should.equal('doc'); res.rows[0].key.should.equal('zoot'); return db.bulkDocs([ { _id: 'doc', name: 'suit', _rev: '2-w', _revisions: { start: 2, ids: ['w', 'y'] } }, { _id: 'doc', name: 'zorb', _rev: '2-z', _revisions: { start: 2, ids: ['z', 'y'] } } ], {new_edits: false}); }).then(function () { return db.query(mapFun); }).then(function (res) { res.rows.should.have.length(1); res.rows[0].id.should.equal('doc'); res.rows[0].key.should.equal('zorb'); }); }); }); it('Testing ordering with startkey/endkey/key', function () { var opts = {startkey: '1', endkey: '4'}; function ids(row) { return row.id; } var spec; return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.field, null); } }).then(function (mapFunction) { return db.bulkDocs({ docs: [ {_id: 'h', field: '4'}, {_id: 'a', field: '1'}, {_id: 'e', field: '2'}, {_id: 'c', field: '1'}, {_id: 'f', field: '3'}, {_id: 'g', field: '4'}, {_id: 'd', field: '2'}, {_id: 'b', field: '1'} ] }).then(function () { spec = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); opts = {key: '1'}; spec = ['a', 'b', 'c']; return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); opts = {key: '2'}; spec = ['d', 'e']; return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec); opts.descending = true; spec.reverse(); return db.query(mapFunction, opts); }).then(function (data) { data.rows.map(ids).should.deep.equal(spec, 'reverse order'); }); }); }); }); it('opts.keys should work with complex keys', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.foo, doc.foo); } }).then(function (mapFunction) { var keys = [ {key: 'missing'}, ['test', 1], {key1: 'value1'}, ['missing'], [0, 0] ]; return db.bulkDocs({ docs: [ {foo: {key2: 'value2'}}, {foo: {key1: 'value1'}}, {foo: [0, 0]}, {foo: ['test', 1]}, {foo: [0, false]} ] }).then(function () { var opts = {keys: keys}; return db.query(mapFunction, opts); }).then(function (data) { data.rows.should.have.length(3); data.rows[0].value.should.deep.equal(keys[1]); data.rows[1].value.should.deep.equal(keys[2]); data.rows[2].value.should.deep.equal(keys[4]); }); }); }); }); it('Testing ordering with dates', function () { function ids(row) { return row.id; } return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.date, null); } }).then(function (mapFunction) { return db.bulkDocs({ docs: [ {_id: '1969', date: '1969 was when Space Oddity hit'}, {_id: '1971', date : new Date('1971-12-17T00:00:00.000Z')}, // Hunky Dory was released {_id: '1972', date: '1972 was when Ziggy landed on Earth'}, {_id: '1977', date: new Date('1977-01-14T00:00:00.000Z')}, // Low was released {_id: '1985', date: '1985+ is better left unmentioned'} ] }).then(function () { return db.query(mapFunction); }).then(function (data) { data.rows.map(ids).should.deep.equal(['1969', '1971', '1972', '1977', '1985']); }); }); }); }); it('should work with a joined doc', function () { function change(row) { return [row.key, row.doc._id, row.doc.val]; } return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { if (doc.join) { emit(doc.color, {_id : doc.join}); } } }).then(function (mapFunction) { return db.bulkDocs({ docs: [ {_id: 'a', join: 'b', color: 'green'}, {_id: 'b', val: 'c'}, {_id: 'd', join: 'f', color: 'red'} ] }).then(function () { return db.query(mapFunction, {include_docs: true}); }).then(function (resp) { return change(resp.rows[0]).should.deep.equal(['green', 'b', 'c']); }); }); }); }); it('should query correctly with a variety of criteria', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc._id); } }).then(function (mapFun) { var docs = [ {_id : '0'}, {_id : '1'}, {_id : '2'}, {_id : '3'}, {_id : '4'}, {_id : '5'}, {_id : '6'}, {_id : '7'}, {_id : '8'}, {_id : '9'} ]; return db.bulkDocs({docs : docs}).then(function (res) { docs[3]._deleted = true; docs[7]._deleted = true; docs[3]._rev = res[3].rev; docs[7]._rev = res[7].rev; return db.remove(docs[3]); }).then(function () { return db.remove(docs[7]); }).then(function () { return db.query(mapFun, {}); }).then(function (res) { res.rows.should.have.length(8, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '5'}); }).then(function (res) { res.rows.should.have.length(4, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '5', skip : 2, limit : 10}); }).then(function (res) { res.rows.should.have.length(2, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '5', descending : true, skip : 1}); }).then(function (res) { res.rows.should.have.length(4, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '5', endkey : 'z'}); }).then(function (res) { res.rows.should.have.length(4, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '5', endkey : '5'}); }).then(function (res) { res.rows.should.have.length(1, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '5', endkey : '4', descending : true}); }).then(function (res) { res.rows.should.have.length(2, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '3', endkey : '7', descending : false}); }).then(function (res) { res.rows.should.have.length(3, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '7', endkey : '3', descending : true}); }).then(function (res) { res.rows.should.have.length(3, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '', endkey : '0'}); }).then(function (res) { res.rows.should.have.length(1, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {keys : ['0', '1', '3']}); }).then(function (res) { res.rows.should.have.length(2, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {keys : ['0', '1', '0', '2', '1', '1']}); }).then(function (res) { res.rows.should.have.length(6, 'correctly return rows'); res.rows.map(function (row) { return row.key; }).should.deep.equal( ['0', '1', '0', '2', '1', '1']); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {keys : []}); }).then(function (res) { res.rows.should.have.length(0, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {keys : ['7']}); }).then(function (res) { res.rows.should.have.length(0, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {key : '3'}); }).then(function (res) { res.rows.should.have.length(0, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {key : '2'}); }).then(function (res) { res.rows.should.have.length(1, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {key : 'z'}); }).then(function (res) { res.rows.should.have.length(0, 'correctly return rows'); res.total_rows.should.equal(8, 'correctly return total_rows'); return db.query(mapFun, {startkey : '5', endkey : '4'}).then(function (res) { res.should.not.exist('expected error on reversed start/endkey'); }).catch(function (err) { err.status.should.equal(400); err.message.should.be.a('string'); }); }); }); }); }); it('should query correctly with skip/limit and multiple keys/values', function () { var db = new PouchDB(dbName); var docs = { docs: [ {_id: 'doc1', foo : 'foo', bar : 'bar'}, {_id: 'doc2', foo : 'foo', bar : 'bar'} ] }; var getValues = function (res) { return res.value; }; var getIds = function (res) { return res.id; }; return createView(db, { map : function (doc) { emit(doc.foo, 'fooValue'); emit(doc.foo); emit(doc.bar); emit(doc.bar, 'crayon!'); emit(doc.bar, 'multiple values!'); emit(doc.bar, 'crayon!'); } }).then(function (mapFun) { return db.bulkDocs(docs).then(function () { return db.query(mapFun, {}); }).then(function (res) { res.rows.should.have.length(12, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); res.rows.map(getValues).should.deep.equal( [null, 'crayon!', 'crayon!', 'multiple values!', null, 'crayon!', 'crayon!', 'multiple values!', null, 'fooValue', null, 'fooValue']); res.rows.map(getIds).should.deep.equal( ['doc1', 'doc1', 'doc1', 'doc1', 'doc2', 'doc2', 'doc2', 'doc2', 'doc1', 'doc1', 'doc2', 'doc2']); return db.query(mapFun, {startkey : 'foo'}); }).then(function (res) { res.rows.should.have.length(4, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); res.rows.map(getValues).should.deep.equal( [null, 'fooValue', null, 'fooValue']); res.rows.map(getIds).should.deep.equal( ['doc1', 'doc1', 'doc2', 'doc2']); return db.query(mapFun, {startkey : 'foo', endkey : 'foo'}); }).then(function (res) { res.rows.should.have.length(4, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); return db.query(mapFun, {startkey : 'bar', endkey : 'bar'}); }).then(function (res) { res.rows.should.have.length(8, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); return db.query(mapFun, {startkey : 'foo', limit : 1}); }).then(function (res) { res.rows.should.have.length(1, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); res.rows.map(getValues).should.deep.equal([null]); res.rows.map(getIds).should.deep.equal(['doc1']); return db.query(mapFun, {startkey : 'foo', limit : 2}); }).then(function (res) { res.rows.should.have.length(2, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); return db.query(mapFun, {startkey : 'foo', limit : 1000}); }).then(function (res) { res.rows.should.have.length(4, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); return db.query(mapFun, {startkey : 'foo', skip : 1}); }).then(function (res) { res.rows.should.have.length(3, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); return db.query(mapFun, {startkey : 'foo', skip : 3, limit : 0}); }).then(function (res) { res.rows.should.have.length(0, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); return db.query(mapFun, {startkey : 'foo', skip : 3, limit : 1}); }).then(function (res) { res.rows.should.have.length(1, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); res.rows.map(getValues).should.deep.equal(['fooValue']); res.rows.map(getIds).should.deep.equal(['doc2']); return db.query(mapFun, {startkey : 'quux', skip : 3, limit : 1}); }).then(function (res) { res.rows.should.have.length(0, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); return db.query(mapFun, {startkey : 'bar', limit : 2}); }).then(function (res) { res.rows.should.have.length(2, 'correctly return rows'); res.total_rows.should.equal(12, 'correctly return total_rows'); }); }); }); it('should query correctly with undefined key/values', function () { var db = new PouchDB(dbName); var docs = { docs: [ {_id: 'doc1'}, {_id: 'doc2'} ] }; return createView(db, { map : function () { emit(); } }).then(function (mapFun) { return db.bulkDocs(docs).then(function () { return db.query(mapFun, {}); }).then(function (res) { res.total_rows.should.equal(2, 'correctly return total_rows'); res.rows.should.deep.equal([ { key : null, value : null, id : 'doc1' }, { key : null, value : null, id : 'doc2' } ]); }); }); }); it('should query correctly with no docs', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function () { emit(); } }).then(function (queryFun) { return db.query(queryFun).then(function (res) { res.total_rows.should.equal(0, 'total_rows'); res.offset.should.equal(0); res.rows.should.deep.equal([]); }); }); }); }); it('should query correctly with no emits', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function () { } }).then(function (queryFun) { return db.bulkDocs({docs : [ {_id : 'foo'}, {_id : 'bar'} ]}).then(function () { return db.query(queryFun).then(function (res) { res.total_rows.should.equal(0, 'total_rows'); res.offset.should.equal(0); res.rows.should.deep.equal([]); }); }); }); }); }); it('should correctly return results when reducing or not reducing', function () { function keyValues(row) { return { key: row.key, value: row.value }; } function keys(row) { return row.key; } function values(row) { return row.value; } function docIds(row) { return row.doc._id; } return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.name); }, reduce : '_count' }).then(function (queryFun) { return db.bulkDocs({docs : [ {name : 'foo', _id : '1'}, {name : 'bar', _id : '2'}, {name : 'foo', _id : '3'}, {name : 'quux', _id : '4'}, {name : 'foo', _id : '5'}, {name : 'foo', _id : '6'}, {name : 'foo', _id : '7'} ]}).then(function () { return db.query(queryFun); }).then(function (res) { Object.keys(res.rows[0]).sort().should.deep.equal(['key', 'value'], 'object only have 2 keys'); should.not.exist(res.total_rows, 'no total_rows1'); should.not.exist(res.offset, 'no offset1'); res.rows.map(keyValues).should.deep.equal([ { key : null, value : 7 } ]); return db.query(queryFun, {group : true}); }).then(function (res) { Object.keys(res.rows[0]).sort().should.deep.equal(['key', 'value'], 'object only have 2 keys'); should.not.exist(res.total_rows, 'no total_rows2'); should.not.exist(res.offset, 'no offset2'); res.rows.map(keyValues).should.deep.equal([ { key : 'bar', value : 1 }, { key : 'foo', value : 5 }, { key : 'quux', value : 1 } ]); return db.query(queryFun, {reduce : false}); }).then(function (res) { Object.keys(res.rows[0]).sort().should.deep.equal(['id', 'key', 'value'], 'object only have 3 keys'); res.total_rows.should.equal(7, 'total_rows1'); res.offset.should.equal(0, 'offset1'); res.rows.map(keys).should.deep.equal([ 'bar', 'foo', 'foo', 'foo', 'foo', 'foo', 'quux' ]); res.rows.map(values).should.deep.equal([ null, null, null, null, null, null, null ]); return db.query(queryFun, {reduce : false, skip : 3}); }).then(function (res) { Object.keys(res.rows[0]).sort().should.deep.equal(['id', 'key', 'value'], 'object only have 3 keys'); res.total_rows.should.equal(7, 'total_rows2'); res.offset.should.equal(3, 'offset2'); res.rows.map(keys).should.deep.equal([ 'foo', 'foo', 'foo', 'quux' ]); return db.query(queryFun, {reduce : false, include_docs : true}); }).then(function (res) { Object.keys(res.rows[0]).sort().should.deep.equal(['doc', 'id', 'key', 'value'], 'object only have 4 keys'); res.total_rows.should.equal(7, 'total_rows3'); res.offset.should.equal(0, 'offset3'); res.rows.map(keys).should.deep.equal([ 'bar', 'foo', 'foo', 'foo', 'foo', 'foo', 'quux' ]); res.rows.map(values).should.deep.equal([ null, null, null, null, null, null, null ]); res.rows.map(docIds).should.deep.equal([ '2', '1', '3', '5', '6', '7', '4' ]); return db.query(queryFun, {include_docs : true}).then(function (res) { should.not.exist(res); }).catch(function (err) { err.status.should.equal(400); err.message.should.be.a('string'); // include_docs is invalid for reduce }); }); }); }); }); it('should query correctly after replicating and other ddoc', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.name); } }).then(function (queryFun) { return db.bulkDocs({docs: [{name: 'foobar'}]}).then(function () { return db.query(queryFun); }).then(function (res) { res.rows.map(function (x) {return x.key; }).should.deep.equal([ 'foobar' ], 'test db before replicating'); return new PouchDB('local-other').then(function (db2) { return db.replicate.to(db2).then(function () { return db.query(queryFun); }).then(function (res) { res.rows.map(function (x) {return x.key; }).should.deep.equal([ 'foobar' ], 'test db after replicating'); return db.put({_id: '_design/other_ddoc', views: { map: "function(doc) { emit(doc._id); }" }}); }).then(function () { // the random ddoc adds a single change that we don't // care about. testing this increases our coverage return db.query(queryFun); }).then(function (res) { res.rows.map(function (x) {return x.key; }).should.deep.equal([ 'foobar' ], 'test db after adding random ddoc'); return db2.query(queryFun); }).then(function (res) { res.rows.map(function (x) {return x.key; }).should.deep.equal([ 'foobar' ], 'test db2'); }).catch(function (err) { return new PouchDB('local-other').destroy().then(function () { throw err; }); }).then(function () { return new PouchDB('local-other').destroy(); }); }); }); }); }); }); it.skip('should query correctly after many edits', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.name, doc.likes); } }).then(function (queryFun) { var docs = [ { _id: '1', name: 'leonardo' }, { _id: '2', name: 'michelangelo' }, { _id: '3', name: 'donatello' }, { _id: '4', name: 'rafael' }, { _id: '5', name: 'april o\'neil' }, { _id: '6', name: 'splinter' }, { _id: '7', name: 'shredder' }, { _id: '8', name: 'krang' }, { _id: '9', name: 'rocksteady' }, { _id: 'a', name: 'bebop' }, { _id: 'b', name: 'casey jones' }, { _id: 'c', name: 'casey jones' }, { _id: 'd', name: 'baxter stockman' }, { _id: 'e', name: 'general chaos' }, { _id: 'f', name: 'rahzar' }, { _id: 'g', name: 'tokka' }, { _id: 'h', name: 'usagi yojimbo' }, { _id: 'i', name: 'rat king' }, { _id: 'j', name: 'metalhead' }, { _id: 'k', name: 'slash' }, { _id: 'l', name: 'ace duck' } ]; for (var i = 0; i < 100; i++) { docs.push({ _id: 'z-' + (i + 1000), // for correct string ordering name: 'random foot soldier #' + i }); } function update(res, docFun) { for (var i = 0; i < res.length; i++) { docs[i]._rev = res[i].rev; docFun(docs[i]); } return db.bulkDocs({docs : docs}); } return db.bulkDocs({docs : docs}).then(function (res) { return update(res, function (doc) { doc.likes = 'pizza'; }); }).then(function (res) { return update(res, function (doc) { doc.knows = 'kung fu'; }); }).then(function (res) { return update(res, function (doc) { doc.likes = 'fighting'; }); }).then(function (res) { return update(res, function (doc) { doc._deleted = true; }); }).then(function (res) { return update(res, function (doc) { doc._deleted = false; }); }).then(function (res) { return update(res, function (doc) { doc.name = doc.name + '1'; }); }).then(function (res) { return update(res, function (doc) { doc.name = doc.name + '2'; }); }).then(function (res) { return update(res, function (doc) { doc.name = 'nameless'; }); }).then(function (res) { return update(res, function (doc) { doc._deleted = true; }); }).then(function (res) { return update(res, function (doc) { doc.likes = 'turtles'; }); }).then(function (res) { return update(res, function (doc) { doc._deleted = false; }); }).then(function (res) { return update(res, function (doc) { doc.whatever = 'quux'; }); }).then(function (res) { return update(res, function (doc) { doc.stuff = 'baz'; }); }).then(function (res) { return update(res, function (doc) { doc.things = 'foo'; }); }).then(function () { return db.query(queryFun); }).then(function (res) { res.total_rows.should.equal(docs.length, 'expected total_rows'); res.rows.map(function (row) { return [row.id, row.key, row.value]; }).should.deep.equal(docs.map(function (doc) { return [doc._id, 'nameless', 'turtles']; }), 'key values match'); }); }); }); }); it.skip('should query correctly with staggered seqs', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.name); } }).then(function (queryFun) { var docs = []; for (var i = 0; i < 200; i++) { docs.push({ _id: 'doc-' + (i + 1000), // for correct string ordering name: 'gen1' }); } return db.bulkDocs({docs: docs}).then(function (infos) { docs.forEach(function (doc, i) { doc._rev = infos[i].rev; doc.name = 'gen2'; }); docs.reverse(); return db.bulkDocs({docs: docs}); }).then(function (infos) { docs.forEach(function (doc, i) { doc._rev = infos[i].rev; doc.name = 'gen-3'; }); docs.reverse(); return db.bulkDocs({docs: docs}); }).then(function (infos) { docs.forEach(function (doc, i) { doc._rev = infos[i].rev; doc.name = 'gen-4-odd'; }); var docsToUpdate = docs.filter(function (doc, i) { return i % 2 === 1; }); docsToUpdate.reverse(); return db.bulkDocs({docs: docsToUpdate}); }).then(function () { return db.query(queryFun); }).then(function (res) { var expected = docs.map(function (doc, i) { var key = i % 2 === 1 ? 'gen-4-odd' : 'gen-3'; return {key: key, id: doc._id, value: null}; }); expected.sort(function (a, b) { if (a.key !== b.key) { return a.key < b.key ? -1 : 1; } return a.id < b.id ? -1 : 1; }); res.rows.should.deep.equal(expected); }); }); }); }); it('should handle removes/undeletes/updates', function () { var theDoc = {name : 'bar', _id : '1'}; return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.name); } }).then(function (queryFun) { return db.put(theDoc).then(function (info) { theDoc._rev = info.rev; return db.query(queryFun); }).then(function (res) { res.rows.length.should.equal(1); theDoc._deleted = true; return db.post(theDoc); }).then(function (info) { theDoc._rev = info.rev; return db.query(queryFun); }).then(function (res) { res.rows.length.should.equal(0); theDoc._deleted = false; return db.post(theDoc); }).then(function (info) { theDoc._rev = info.rev; return db.query(queryFun); }).then(function (res) { res.rows.length.should.equal(1); theDoc.name = 'foo'; return db.post(theDoc); }).then(function (info) { theDoc._rev = info.rev; return db.query(queryFun); }).then(function (res) { res.rows.length.should.equal(1); res.rows[0].key.should.equal('foo'); theDoc._deleted = true; return db.post(theDoc); }).then(function (info) { theDoc._rev = info.rev; return db.query(queryFun); }).then(function (res) { res.rows.length.should.equal(0); }); }); }); }); it('should return error when multi-key fetch & group=false', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc._id); }, reduce: '_sum' }).then(function (queryFun) { var keys = ['1', '2']; var opts = { keys: keys, group: false }; return db.query(queryFun, opts).then(function (res) { should.not.exist(res); }).catch(function (err) { err.status.should.equal(400); opts = {keys: keys}; return db.query(queryFun, opts).then(function (res) { should.not.exist(res); }).catch(function (err) { err.status.should.equal(400); opts = {keys: keys, reduce : false}; return db.query(queryFun, opts).then(function () { opts = {keys: keys, group: true}; return db.query(queryFun, opts); }); }); }); }); }); }); it('should handle user errors in map functions', function () { return new PouchDB(dbName).then(function (db) { var err; db.on('error', function (e) { err = e; }); return createView(db, { map : function (doc) { emit(doc.nonexistent.foo); } }).then(function (queryFun) { return db.put({name : 'bar', _id : '1'}).then(function () { return db.query(queryFun); }).then(function (res) { res.rows.should.have.length(0); if (dbType === 'local') { should.exist(err); } }); }); }); }); it('should handle user errors in reduce functions', function () { return new PouchDB(dbName).then(function (db) { var err; db.on('error', function (e) { err = e; }); return createView(db, { map : function (doc) { emit(doc.name); }, reduce : function (keys) { return keys[0].foo.bar; } }).then(function (queryFun) { return db.put({name : 'bar', _id : '1'}).then(function () { return db.query(queryFun, {group: true}); }).then(function (res) { res.rows.map(function (row) {return row.key; }).should.deep.equal(['bar']); return db.query(queryFun, {reduce: false}); }).then(function (res) { res.rows.map(function (row) {return row.key; }).should.deep.equal(['bar']); if (dbType === 'local') { should.exist(err); } }); }); }); }); it('should handle reduce returning undefined', function () { return new PouchDB(dbName).then(function (db) { var err; db.on('error', function (e) { err = e; }); return createView(db, { map : function (doc) { emit(doc.name); }, reduce : function () { } }).then(function (queryFun) { return db.put({name : 'bar', _id : '1'}).then(function () { return db.query(queryFun, {group: true}); }).then(function (res) { res.rows.map(function (row) {return row.key; }).should.deep.equal(['bar']); return db.query(queryFun, {reduce: false}); }).then(function (res) { res.rows.map(function (row) {return row.key; }).should.deep.equal(['bar']); should.not.exist(err); }); }); }); }); it('should properly query custom reduce functions', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc.name, doc.count); }, reduce : function (keys, values, rereduce) { // calculate the average count per name if (!rereduce) { var result = { sum : sum(values), count : values.length }; result.average = result.sum / result.count; return result; } else { var thisSum = sum(values.map(function (value) {return value.sum; })); var thisCount = sum(values.map(function (value) {return value.count; })); return { sum : thisSum, count : thisCount, average : (thisSum / thisCount) }; } } }).then(function (queryFun) { return db.bulkDocs({docs : [ {name : 'foo', count : 1}, {name : 'bar', count : 7}, {name : 'foo', count : 3}, {name : 'quux', count : 3}, {name : 'foo', count : 3}, {name : 'foo', count : 0}, {name : 'foo', count : 4}, {name : 'baz', count : 3}, {name : 'baz', count : 0}, {name : 'baz', count : 2} ]}).then(function () { return db.query(queryFun, {group : true}); }).then(function (res) { res.should.deep.equal({rows : [ { key : 'bar', value : { sum: 7, count: 1, average : 7} }, { key : 'baz', value : { sum: 5, count: 3, average: (5 / 3) } }, { key : 'foo', value : { sum: 11, count: 5, average: (11 / 5) } }, { key : 'quux', value : { sum: 3, count: 1, average: 3 } } ]}, 'all'); return db.query(queryFun, {group : false}); }).then(function (res) { res.should.deep.equal({rows : [ { key : null, value : { sum: 26, count: 10, average: 2.6 } } ]}, 'group=false'); return db.query(queryFun, {group : true, startkey : 'bar', endkey : 'baz', skip : 1}); }).then(function (res) { res.should.deep.equal({rows : [ { key : 'baz', value : { sum: 5, count: 3, average: (5 / 3) } } ]}, 'bar-baz skip 1'); return db.query(queryFun, {group : true, endkey : 'baz'}); }).then(function (res) { res.should.deep.equal({rows : [ { key : 'bar', value : { sum: 7, count: 1, average : 7} }, { key : 'baz', value : { sum: 5, count: 3, average: (5 / 3) } } ]}, '-baz'); return db.query(queryFun, {group : true, startkey : 'foo'}); }).then(function (res) { res.should.deep.equal({rows : [ { key : 'foo', value : { sum: 11, count: 5, average: (11 / 5) } }, { key : 'quux', value : { sum: 3, count: 1, average: 3 } } ]}, 'foo-'); return db.query(queryFun, {group : true, startkey : 'foo', descending : true}); }).then(function (res) { res.should.deep.equal({rows : [ { key : 'foo', value : { sum: 11, count: 5, average: (11 / 5) } }, { key : 'baz', value : { sum: 5, count: 3, average: (5 / 3) } }, { key : 'bar', value : { sum: 7, count: 1, average : 7} } ]}, 'foo- descending=true'); return db.query(queryFun, {group : true, startkey : 'quux', skip : 1}); }).then(function (res) { res.should.deep.equal({rows : [ ]}, 'quux skip 1'); return db.query(queryFun, {group : true, startkey : 'quux', limit : 0}); }).then(function (res) { res.should.deep.equal({rows : [ ]}, 'quux limit 0'); return db.query(queryFun, {group : true, startkey : 'bar', endkey : 'baz'}); }).then(function (res) { res.should.deep.equal({rows : [ { key : 'bar', value : { sum: 7, count: 1, average : 7} }, { key : 'baz', value : { sum: 5, count: 3, average: (5 / 3) } } ]}, 'bar-baz'); return db.query(queryFun, {group : true, keys : ['bar', 'baz'], limit : 1}); }).then(function (res) { res.should.deep.equal({rows : [ { key : 'bar', value : { sum: 7, count: 1, average : 7} } ]}, 'bar & baz'); return db.query(queryFun, {group : true, keys : ['bar', 'baz'], limit : 0}); }).then(function (res) { res.should.deep.equal({rows : [ ]}, 'bar & baz limit 0'); return db.query(queryFun, {group : true, key : 'bar', limit : 0}); }).then(function (res) { res.should.deep.equal({rows : [ ]}, 'key=bar limit 0'); return db.query(queryFun, {group : true, key : 'bar'}); }).then(function (res) { res.should.deep.equal({rows : [ { key : 'bar', value : { sum: 7, count: 1, average : 7} } ]}, 'key=bar'); return db.query(queryFun, {group : true, key : 'zork'}); }).then(function (res) { res.should.deep.equal({rows : [ ]}, 'zork'); return db.query(queryFun, {group : true, keys : []}); }).then(function (res) { res.should.deep.equal({rows : [ ]}, 'keys=[]'); return db.query(queryFun, {group : true, key : null}); }).then(function (res) { res.should.deep.equal({rows : [ ]}, 'key=null'); }); }); }); }); it.skip('should handle many doc changes', function () { var docs = [{_id: '0'}, {_id : '1'}, {_id: '2'}]; var keySets = [ [1], [2, 3], [4], [5], [6, 7, 3], [], [2, 3], [1, 2], [], [9], [9, 3, 2, 1] ]; return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { doc.keys.forEach(function (key) { emit(key); }); } }).then(function (mapFun) { return db.bulkDocs({docs : docs}).then(function () { var tasks = keySets.map(function (keys, i) { return function () { var expectedResponseKeys = []; return db.allDocs({ keys : ['0', '1', '2'], include_docs: true }).then(function (res) { docs = res.rows.map(function (x) { return x.doc; }); docs.forEach(function (doc, j) { doc.keys = keySets[(i + j) % keySets.length]; doc.keys.forEach(function (key) { expectedResponseKeys.push(key); }); }); expectedResponseKeys.sort(); return db.bulkDocs({docs: docs}); }).then(function () { return db.query(mapFun); }).then(function (res) { var actualKeys = res.rows.map(function (x) { return x.key; }); actualKeys.should.deep.equal(expectedResponseKeys); }); }; }); var chain = tasks.shift()(); function getNext() { var task = tasks.shift(); return task && function () { return task().then(getNext()); }; } return chain.then(getNext()); }); }); }); }); it('should handle many doc changes', function () { var docs = [{_id: '0'}, {_id : '1'}, {_id: '2'}]; var keySets = [ [1], [2, 3], [4], [5], [6, 7, 3], [], [2, 3], [1, 2], [], [9], [9, 3, 2, 1] ]; return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { doc.keys.forEach(function (key) { emit(key); }); } }).then(function (mapFun) { return db.bulkDocs({docs : docs}).then(function () { var tasks = keySets.map(function (keys, i) { return function () { var expectedResponseKeys = []; return db.allDocs({ keys : ['0', '1', '2'], include_docs: true }).then(function (res) { docs = res.rows.map(function (x) { return x.doc; }); docs.forEach(function (doc, j) { doc.keys = keySets[(i + j) % keySets.length]; doc.keys.forEach(function (key) { expectedResponseKeys.push(key); }); }); expectedResponseKeys.sort(function (a, b) { return a - b; }); return db.bulkDocs({docs: docs}); }).then(function () { return db.query(mapFun); }).then(function (res) { var actualKeys = res.rows.map(function (x) { return x.key; }); actualKeys.should.deep.equal(expectedResponseKeys); }); }; }); function getNext() { var task = tasks.shift(); if (task) { return task().then(getNext); } } return getNext(); }); }); }); }); it('should work with post', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map : function (doc) { emit(doc._id); }.toString() }).then(function (mapFun) { return db.bulkDocs({docs: [{_id : 'bazbazbazb'}]}).then(function () { var i = 300; var keys = []; while (i--) { keys.push('bazbazbazb'); } return db.query(mapFun, {keys: keys}).then(function (resp) { resp.total_rows.should.equal(1); resp.rows.should.have.length(300); return resp.rows.every(function (row) { return row.id === 'bazbazbazb' && row.key === 'bazbazbazb'; }); }); }).should.become(true); }); }); }); it("should accept trailing ';' in a map definition (#178)", function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: "function(doc){};\n" }).then(function (queryFun) { return db.query(queryFun); }).should.become({ offset: 0, rows: [], total_rows: 0 }); }); }); it('should throw a 404 when no funcs found in ddoc (#181)', function () { return new PouchDB(dbName).then(function (db) { return db.put({ _id: '_design/test' }).then(function () { return db.query('test/unexisting'); }).then(function () { //shouldn't happen true.should.equal(false); }).catch(function (err) { err.status.should.equal(404); }); }); }); it.skip('should continue indexing when map eval fails (#214)', function () { return new PouchDB(dbName).then(function (db) { var err; db.on('error', function (e) { err = e; }); return createView(db, { map: function (doc) { emit(doc.foo.bar, doc); } }).then(function (view) { return db.bulkDocs({docs: [ { foo: { bar: "foobar" } }, { notfoo: "thisWillThrow" }, { foo: { bar: "otherFoobar" } } ]}).then(function () { return db.query(view); }).then(function (res) { if (dbType === 'local') { should.exist(err); } res.rows.should.have.length(2, 'Ignore the wrongly formatted doc'); return db.query(view); }).then(function (res) { res.rows.should.have.length(2, 'Ignore the wrongly formatted doc'); }); }); }); }); it.skip('should continue indexing when map eval fails, ' + 'even without a listener (#214)', function () { return new PouchDB(dbName).then(function (db) { return createView(db, { map: function (doc) { emit(doc.foo.bar, doc); } }).then(function (view) { return db.bulkDocs({docs: [ { foo: { bar: "foobar" } }, { notfoo: "thisWillThrow" }, { foo: { bar: "otherFoobar" } } ]}).then(function () { return db.query(view); }).then(function (res) { res.rows.should.have.length(2, 'Ignore the wrongly formatted doc'); return db.query(view); }).then(function (res) { res.rows.should.have.length(2, 'Ignore the wrongly formatted doc'); }); }); }); }); it.skip('should update the emitted value', function () { return new PouchDB(dbName).then(function (db) { var docs = []; for (var i = 0; i < 300; i++) { docs.push({ _id: i.toString(), name: 'foo', count: 1 }); } return createView(db, { map: "function(doc){emit(doc.name, doc.count);};\n" }).then(function (queryFun) { return db.bulkDocs({docs: docs}).then(function (res) { for (var i = 0; i < res.length; i++) { docs[i]._rev = res[i].rev; } return db.query(queryFun); }).then(function (res) { var values = res.rows.map(function (x) { return x.value; }); values.should.have.length(docs.length); values[0].should.equal(1); docs.forEach(function (doc) { doc.count = 2; }); return db.bulkDocs({docs: docs}); }).then(function () { return db.query(queryFun); }).then(function (res) { var values = res.rows.map(function (x) { return x.value; }); values.should.have.length(docs.length); values[0].should.equal(2); }); }); }); }); }); }