pouchdb-find
Version:
Easy-to-use query language for PouchDB
1,382 lines (1,314 loc) • 113 kB
JavaScript
/* global sum */
'use strict';
var testUtils = require('../test-utils');
var should = testUtils.should;
module.exports = function tests(dbName, dbType, viewType, PouchDB) {
var suiteName = 'test.mapreduce.js-' + 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.skip("#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("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("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("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('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 = [
"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"
];
/* 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 () {
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,
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 () {
var db = new PouchDB(dbName);
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 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: 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);
});
});
});
});
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 () {
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.rows.should.have.length(1, 'Correctly limits returned rows');
res.rows[0].key.should.equal('baz');
res.rows[0].value.should.equal(1);
});
});
});
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('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.hav