UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

239 lines (231 loc) 7.35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getQueryMatcher = getQueryMatcher; exports.getSortComparator = getSortComparator; exports.normalizeMangoQuery = normalizeMangoQuery; exports.prepareQuery = prepareQuery; exports.runQueryUpdateFunction = runQueryUpdateFunction; var _queryPlanner = require("./query-planner.js"); var _rxSchemaHelper = require("./rx-schema-helper.js"); var _index = require("./plugins/utils/index.js"); var _util = require("mingo/util"); var _rxError = require("./rx-error.js"); var _rxQueryMingo = require("./rx-query-mingo.js"); /** * Normalize the query to ensure we have all fields set * and queries that represent the same query logic are detected as equal by the caching. */ function normalizeMangoQuery(schema, mangoQuery) { var primaryKey = (0, _rxSchemaHelper.getPrimaryFieldOfPrimaryKey)(schema.primaryKey); mangoQuery = (0, _index.flatClone)(mangoQuery); var normalizedMangoQuery = (0, _index.clone)(mangoQuery); if (typeof normalizedMangoQuery.skip !== 'number') { normalizedMangoQuery.skip = 0; } if (!normalizedMangoQuery.selector) { normalizedMangoQuery.selector = {}; } else { normalizedMangoQuery.selector = normalizedMangoQuery.selector; /** * In mango query, it is possible to have an * equals comparison by directly assigning a value * to a property, without the '$eq' operator. * Like: * selector: { * foo: 'bar' * } * For normalization, we have to normalize this * so our checks can perform properly. * * * TODO this must work recursive with nested queries that * contain multiple selectors via $and or $or etc. */ Object.entries(normalizedMangoQuery.selector).forEach(([field, matcher]) => { if (typeof matcher !== 'object' || matcher === null) { normalizedMangoQuery.selector[field] = { $eq: matcher }; } }); } /** * Ensure that if an index is specified, * the primaryKey is inside of it. */ if (normalizedMangoQuery.index) { var indexAr = (0, _index.toArray)(normalizedMangoQuery.index); if (!indexAr.includes(primaryKey)) { indexAr.push(primaryKey); } normalizedMangoQuery.index = indexAr; } /** * To ensure a deterministic sorting, * we have to ensure the primary key is always part * of the sort query. * Primary sorting is added as last sort parameter, * similar to how we add the primary key to indexes that do not have it. * */ if (!normalizedMangoQuery.sort) { /** * If no sort is given at all, * we can assume that the user does not care about sort order at al. * * we cannot just use the primary key as sort parameter * because it would likely cause the query to run over the primary key index * which has a bad performance in most cases. */ if (normalizedMangoQuery.index) { normalizedMangoQuery.sort = normalizedMangoQuery.index.map(field => { return { [field]: 'asc' }; }); } else { /** * Find the index that best matches the fields with the logical operators */ if (schema.indexes) { var fieldsWithLogicalOperator = new Set(); Object.entries(normalizedMangoQuery.selector).forEach(([field, matcher]) => { var hasLogical = false; if (typeof matcher === 'object' && matcher !== null) { hasLogical = !!Object.keys(matcher).find(operator => _queryPlanner.LOGICAL_OPERATORS.has(operator)); } else { hasLogical = true; } if (hasLogical) { fieldsWithLogicalOperator.add(field); } }); var currentFieldsAmount = -1; var currentBestIndexForSort; schema.indexes.forEach(index => { var useIndex = (0, _index.isMaybeReadonlyArray)(index) ? index : [index]; var firstWrongIndex = useIndex.findIndex(indexField => !fieldsWithLogicalOperator.has(indexField)); if (firstWrongIndex > 0 && firstWrongIndex > currentFieldsAmount) { currentFieldsAmount = firstWrongIndex; currentBestIndexForSort = useIndex; } }); if (currentBestIndexForSort) { normalizedMangoQuery.sort = currentBestIndexForSort.map(field => { return { [field]: 'asc' }; }); } } /** * Fall back to the primary key as sort order * if no better one has been found */ if (!normalizedMangoQuery.sort) { normalizedMangoQuery.sort = [{ [primaryKey]: 'asc' }]; } } } else { var isPrimaryInSort = normalizedMangoQuery.sort.find(p => (0, _index.firstPropertyNameOfObject)(p) === primaryKey); if (!isPrimaryInSort) { normalizedMangoQuery.sort = normalizedMangoQuery.sort.slice(0); normalizedMangoQuery.sort.push({ [primaryKey]: 'asc' }); } } return normalizedMangoQuery; } /** * Returns the sort-comparator, * which is able to sort documents in the same way * a query over the db would do. */ function getSortComparator(schema, query) { if (!query.sort) { throw (0, _rxError.newRxError)('SNH', { query }); } var sortParts = []; query.sort.forEach(sortBlock => { var key = Object.keys(sortBlock)[0]; var direction = Object.values(sortBlock)[0]; sortParts.push({ key, direction, getValueFn: (0, _index.objectPathMonad)(key) }); }); var fun = (a, b) => { for (var i = 0; i < sortParts.length; ++i) { var sortPart = sortParts[i]; var valueA = sortPart.getValueFn(a); var valueB = sortPart.getValueFn(b); if (valueA !== valueB) { var ret = sortPart.direction === 'asc' ? (0, _util.compare)(valueA, valueB) : (0, _util.compare)(valueB, valueA); return ret; } } }; return fun; } /** * Returns a function * that can be used to check if a document * matches the query. */ function getQueryMatcher(_schema, query) { if (!query.sort) { throw (0, _rxError.newRxError)('SNH', { query }); } var mingoQuery = (0, _rxQueryMingo.getMingoQuery)(query.selector); var fun = doc => { return mingoQuery.test(doc); }; return fun; } async function runQueryUpdateFunction(rxQuery, fn) { var docs = await rxQuery.exec(); if (!docs) { // only findOne() queries can return null return null; } if (Array.isArray(docs)) { return Promise.all(docs.map(doc => fn(doc))); } else if (docs instanceof Map) { return Promise.all([...docs.values()].map(doc => fn(doc))); } else { // via findOne() var result = await fn(docs); return result; } } /** * @returns a format of the query that can be used with the storage * when calling RxStorageInstance().query() */ function prepareQuery(schema, mutateableQuery) { if (!mutateableQuery.sort) { throw (0, _rxError.newRxError)('SNH', { query: mutateableQuery }); } /** * Store the query plan together with the * prepared query to save performance. */ var queryPlan = (0, _queryPlanner.getQueryPlan)(schema, mutateableQuery); return { query: mutateableQuery, queryPlan }; } //# sourceMappingURL=rx-query-helper.js.map