blob: f97d21cf52cf48aa841d81cf7f41b1f85063de7d [file] [log] [blame]
import { collate } from 'pouchdb-collate';
import { clone } from 'pouchdb-utils';
import { getKey, getValue, massageSelector, parseField, getFieldFromDoc } from 'pouchdb-selector-core';
// normalize the "sort" value
function massageSort(sort) {
if (!Array.isArray(sort)) {
throw new Error('invalid sort json - should be an array');
}
return sort.map(function (sorting) {
if (typeof sorting === 'string') {
const obj = {};
obj[sorting] = 'asc';
return obj;
} else {
return sorting;
}
});
}
const ddocIdPrefix = /^_design\//;
function massageUseIndex(useIndex) {
let cleanedUseIndex = [];
if (typeof useIndex === 'string') {
cleanedUseIndex.push(useIndex);
} else {
cleanedUseIndex = useIndex;
}
return cleanedUseIndex.map(function (name) {
return name.replace(ddocIdPrefix, '');
});
}
function massageIndexDef(indexDef) {
indexDef.fields = indexDef.fields.map(function (field) {
if (typeof field === 'string') {
const obj = {};
obj[field] = 'asc';
return obj;
}
return field;
});
if (indexDef.partial_filter_selector) {
indexDef.partial_filter_selector = massageSelector(
indexDef.partial_filter_selector
);
}
return indexDef;
}
function getKeyFromDoc(doc, index) {
return index.def.fields.map((obj) => {
const field = getKey(obj);
return getFieldFromDoc(doc, parseField(field));
});
}
// have to do this manually because REASONS. I don't know why
// CouchDB didn't implement inclusive_start
function filterInclusiveStart(rows, targetValue, index) {
const indexFields = index.def.fields;
let startAt = 0;
for (const row of rows) {
// shave off any docs at the beginning that are <= the
// target value
let docKey = getKeyFromDoc(row.doc, index);
if (indexFields.length === 1) {
docKey = docKey[0]; // only one field, not multi-field
} else { // more than one field in index
// in the case where e.g. the user is searching {$gt: {a: 1}}
// but the index is [a, b], then we need to shorten the doc key
while (docKey.length > targetValue.length) {
docKey.pop();
}
}
//ABS as we just looking for values that don't match
if (Math.abs(collate(docKey, targetValue)) > 0) {
// no need to filter any further; we're past the key
break;
}
++startAt;
}
return startAt > 0 ? rows.slice(startAt) : rows;
}
function reverseOptions(opts) {
const newOpts = clone(opts);
delete newOpts.startkey;
delete newOpts.endkey;
delete newOpts.inclusive_start;
delete newOpts.inclusive_end;
if ('endkey' in opts) {
newOpts.startkey = opts.endkey;
}
if ('startkey' in opts) {
newOpts.endkey = opts.startkey;
}
if ('inclusive_start' in opts) {
newOpts.inclusive_end = opts.inclusive_start;
}
if ('inclusive_end' in opts) {
newOpts.inclusive_start = opts.inclusive_end;
}
return newOpts;
}
function validateIndex(index) {
const ascFields = index.fields.filter(function (field) {
return getValue(field) === 'asc';
});
if (ascFields.length !== 0 && ascFields.length !== index.fields.length) {
throw new Error('unsupported mixed sorting');
}
}
function validateSort(requestDef, index) {
if (index.defaultUsed && requestDef.sort) {
const noneIdSorts = requestDef.sort.filter(function (sortItem) {
return Object.keys(sortItem)[0] !== '_id';
}).map(function (sortItem) {
return Object.keys(sortItem)[0];
});
if (noneIdSorts.length > 0) {
throw new Error('Cannot sort on field(s) "' + noneIdSorts.join(',') +
'" when using the default index');
}
}
if (index.defaultUsed) {
return;
}
}
function validateFindRequest(requestDef) {
if (typeof requestDef.selector !== 'object') {
throw new Error('you must provide a selector when you find()');
}
/*var selectors = requestDef.selector['$and'] || [requestDef.selector];
for (var i = 0; i < selectors.length; i++) {
var selector = selectors[i];
var keys = Object.keys(selector);
if (keys.length === 0) {
throw new Error('invalid empty selector');
}
//var selection = selector[keys[0]];
/*if (Object.keys(selection).length !== 1) {
throw new Error('invalid selector: ' + JSON.stringify(selection) +
' - it must have exactly one key/value');
}
}*/
}
// determine the maximum number of fields
// we're going to need to query, e.g. if the user
// has selection ['a'] and sorting ['a', 'b'], then we
// need to use the longer of the two: ['a', 'b']
function getUserFields(selector, sort) {
const selectorFields = Object.keys(selector);
const sortFields = sort ? sort.map(getKey) : [];
let userFields;
if (selectorFields.length >= sortFields.length) {
userFields = selectorFields;
} else {
userFields = sortFields;
}
if (sortFields.length === 0) {
return {
fields: userFields
};
}
// sort according to the user's preferred sorting
userFields = userFields.sort(function (left, right) {
let leftIdx = sortFields.indexOf(left);
if (leftIdx === -1) {
leftIdx = Number.MAX_VALUE;
}
let rightIdx = sortFields.indexOf(right);
if (rightIdx === -1) {
rightIdx = Number.MAX_VALUE;
}
return leftIdx < rightIdx ? -1 : leftIdx > rightIdx ? 1 : 0;
});
return {
fields: userFields,
sortOrder: sort.map(getKey)
};
}
export {
massageSort,
validateIndex,
validateFindRequest,
validateSort,
reverseOptions,
filterInclusiveStart,
massageIndexDef,
getUserFields,
massageUseIndex
};