@netlify/content-engine
Version:
94 lines • 4.15 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.suggestIndex = suggestIndex;
const query_1 = require("../../common/query");
const common_1 = require("./common");
/**
* Suggest index fields for this combination of filter and sort.
*
* Prioritizes sort fields over filter fields when can't use index
* for both because sorting is expensive both CPU and memory-wise.
*/
function suggestIndex({ filter, sort, maxFields = 6, }) {
const filterQueries = (0, query_1.createDbQueriesFromObject)((0, query_1.prepareQueryArgs)(filter));
const filterQueriesThatCanUseIndex = getQueriesThatCanUseIndex(filterQueries);
const sortFields = getSortFieldsThatCanUseIndex(sort);
if (!sortFields.length && !filterQueriesThatCanUseIndex.length) {
return [];
}
if (!filterQueriesThatCanUseIndex.length) {
return dedupeAndTrim(sortFields, maxFields);
}
if (!sortFields.length) {
return dedupeAndTrim(toIndexFields(filterQueriesThatCanUseIndex), maxFields);
}
// Combined index for filter+sort only makes sense when all prefix fields have `eq` predicate
// Same as https://docs.mongodb.com/manual/tutorial/sort-results-with-indexes/#sort-and-non-prefix-subset-of-an-index
const sortDirection = sortFields[0][1];
const eqFilterQueries = getEqQueries(filterQueriesThatCanUseIndex);
const eqFilterFields = toIndexFields(eqFilterQueries, sortDirection);
// Index prefix should not contain eq filters overlapping with sort fields
const overlap = findOverlappingFields(eqFilterQueries, sortFields);
return dedupeAndTrim([
...eqFilterFields.filter(([name]) => !overlap.has(name)),
...sortFields,
// Still append other filter fields to the tail of the index to leverage additional filtering
// of results using data stored in the index (without loading full node object)
// Note: fields previously listed in eqFilterFields and sortFields will be removed in dedupeAndTrim
...toIndexFields(filterQueriesThatCanUseIndex, sortDirection),
], maxFields);
}
const canUseIndex = new Set([
query_1.DbComparator.EQ,
query_1.DbComparator.IN,
query_1.DbComparator.GTE,
query_1.DbComparator.LTE,
query_1.DbComparator.GT,
query_1.DbComparator.LT,
query_1.DbComparator.NIN,
query_1.DbComparator.NE,
]);
/**
* Returns queries that can potentially use index.
* Returned list is sorted by query specificity
*/
function getQueriesThatCanUseIndex(all) {
return (0, query_1.sortBySpecificity)(all.filter((q) => canUseIndex.has((0, query_1.getFilterStatement)(q).comparator)));
}
function getSortFieldsThatCanUseIndex(querySortArg) {
const sort = querySortArg || { fields: [], order: [] };
const initialOrder = (0, common_1.isDesc)(sort?.order[0]) ? -1 : 1;
const sortFields = [];
for (let i = 0; i < sort.fields.length; i++) {
const field = sort.fields[i];
const order = (0, common_1.isDesc)(sort.order[i]) ? -1 : 1;
if (order !== initialOrder) {
// Mixed sort order is not supported by our indexes yet :/
// See https://github.com/DoctorEvidence/lmdb-store/discussions/62#discussioncomment-898949
break;
}
sortFields.push([field, order]);
}
return sortFields;
}
function findOverlappingFields(filterQueries, sortFields) {
const overlap = new Set();
for (const [fieldName] of sortFields) {
const filterQuery = filterQueries.find((q) => (0, query_1.dbQueryToDottedField)(q) === fieldName);
if (!filterQuery) {
break;
}
overlap.add(fieldName);
}
return overlap;
}
function getEqQueries(filterQueries) {
return filterQueries.filter((filterQuery) => (0, query_1.getFilterStatement)(filterQuery).comparator === query_1.DbComparator.EQ);
}
function toIndexFields(queries, sortDirection = 1) {
return queries.map((q) => [(0, query_1.dbQueryToDottedField)(q), sortDirection]);
}
function dedupeAndTrim(fields, maxFields) {
return [...new Map(fields)].slice(0, maxFields);
}
//# sourceMappingURL=suggest-index.js.map
;