pouchdb-find
Version:
Easy-to-use query language for PouchDB
135 lines (112 loc) • 3.88 kB
JavaScript
;
var utils = require('../../../utils');
var clone = utils.clone;
var getIndexes = require('../get-indexes');
var collate = require('pouchdb-collate').collate;
var abstractMapper = require('../abstract-mapper');
var planQuery = require('./query-planner');
var localUtils = require('../utils');
var filterInMemoryFields = require('./in-memory-filter');
var massageSelector = localUtils.massageSelector;
var massageSort = localUtils.massageSort;
var getValue = localUtils.getValue;
var validateFindRequest = localUtils.validateFindRequest;
var reverseOptions = localUtils.reverseOptions;
var filterInclusiveStart = localUtils.filterInclusiveStart;
var Promise = utils.Promise;
function indexToSignature(index) {
// remove '_design/'
return index.ddoc.substring(8) + '/' + index.name;
}
function doAllDocs(db, originalOpts) {
var opts = clone(originalOpts);
// CouchDB responds in weird ways when you provide a non-string to _id;
// we mimic the behavior for consistency. See issue66 tests for details.
if (opts.descending) {
if ('endkey' in opts && typeof opts.endkey !== 'string') {
opts.endkey = '';
}
if ('startkey' in opts && typeof opts.startkey !== 'string') {
opts.limit = 0;
}
} else {
if ('startkey' in opts && typeof opts.startkey !== 'string') {
opts.startkey = '';
}
if ('endkey' in opts && typeof opts.endkey !== 'string') {
opts.limit = 0;
}
}
if ('key' in opts && typeof opts.key !== 'string') {
opts.limit = 0;
}
return db.allDocs(opts);
}
function find(db, requestDef) {
if (requestDef.selector) {
requestDef.selector = massageSelector(requestDef.selector);
}
if (requestDef.sort) {
requestDef.sort = massageSort(requestDef.sort);
}
validateFindRequest(requestDef);
return getIndexes(db).then(function (getIndexesRes) {
var queryPlan = planQuery(requestDef, getIndexesRes.indexes);
var indexToUse = queryPlan.index;
var opts = utils.extend(true, {
include_docs: true,
reduce: false
}, queryPlan.queryOpts);
if ('startkey' in opts && 'endkey' in opts &&
collate(opts.startkey, opts.endkey) > 0) {
// can't possibly return any results, startkey > endkey
return {docs: []};
}
var isDescending = requestDef.sort &&
typeof requestDef.sort[0] !== 'string' &&
getValue(requestDef.sort[0]) === 'desc';
if (isDescending) {
// either all descending or all ascending
opts.descending = true;
opts = reverseOptions(opts);
}
if (!queryPlan.inMemoryFields.length) {
// no in-memory filtering necessary, so we can let the
// database do the limit/skip for us
if ('limit' in requestDef) {
opts.limit = requestDef.limit;
}
if ('skip' in requestDef) {
opts.skip = requestDef.skip;
}
}
return Promise.resolve().then(function () {
if (indexToUse.name === '_all_docs') {
return doAllDocs(db, opts);
} else {
var signature = indexToSignature(indexToUse);
return abstractMapper.query.call(db, signature, opts);
}
}).then(function (res) {
if (opts.inclusive_start === false) {
// may have to manually filter the first one,
// since couchdb has no true inclusive_start option
res.rows = filterInclusiveStart(res.rows, opts.startkey, indexToUse);
}
if (queryPlan.inMemoryFields.length) {
// need to filter some stuff in-memory
res.rows = filterInMemoryFields(res.rows, requestDef, queryPlan.inMemoryFields);
}
return {
docs: res.rows.map(function (row) {
var doc = row.doc;
if (requestDef.fields) {
return utils.pick(doc, requestDef.fields);
}
return doc;
})
};
});
});
}
module.exports = find;