UNPKG

koncorde

Version:

Supersonic reverse matching engine

420 lines 14.8 kB
"use strict"; /* * Kuzzle, a backend software, self-hostable and ready to use * to power modern apps * * Copyright 2015-2021 Kuzzle * mailto: support AT kuzzle.io * website: http://kuzzle.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OperandsStorage = void 0; const interval_tree_1 = __importDefault(require("@flatten-js/interval-tree")); const regexpCondition_1 = require("./objects/regexpCondition"); const rangeCondition_1 = require("./objects/rangeCondition"); const BoostSpatialIndexImport = __importStar(require("boost-geospatial-index")); const transform_1 = require("../transform"); const hash_1 = require("../util/hash"); const index_1 = require("../index"); const BoostSpatialIndex = BoostSpatialIndexImport.default; /** * Exposes a sets of methods meant to store operands in * the Koncorde keyword-specific part of a field-operand object * * @class OperandsStorage * */ class OperandsStorage { constructor(config) { this.config = config; this.transformer = new transform_1.Transformer(this.config); // I didn't want to, but I had no choice, because the Engine class // cannot be imported in this file, because it would create a circular // dependency. And it does not import it. // So I have to use a require() here, and tell TypeScript to ignore it. // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-var-requires this.Engine = require('./index').Engine; } /** * Stores an empty filter in the <f,o> pairs structure * There can never be more than 1 filter and subfilter for an * all-matching filter * * @param {FieldOperand} operand * @param {object} subfilter */ everything(operand, subfilter) { operand.fields.set('all', [subfilter]); } /** * Stores a "equals" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ equals(operand, subfilter, condition) { const fieldName = Object.keys(condition.value)[0]; const value = condition.value[fieldName]; const field = operand.fields.get(fieldName); if (!field) { operand.fields.set(fieldName, new Map([[value, new Set([subfilter])]])); } else { const entries = field.get(value); if (entries === undefined) { field.set(value, new Set([subfilter])); } else { entries.add(subfilter); } } } /** * Stores a "select" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ select(operand, subfilter, condition) { const fieldName = condition.value.field; const arrayIndex = condition.value.index; const field = operand.fields.get(fieldName); const index = `${fieldName}[${arrayIndex}]`; const normalized = this.transformer.normalize(condition.value.query); const id = (0, hash_1.hash)(this.config.seed, { filter: normalized, index }); const filter = new index_1.NormalizedFilter(normalized, id, index); if (!field) { const engine = new this.Engine(this.config); engine.store(filter); operand.fields.set(fieldName, new Map([[arrayIndex, { engine, filters: new Map([[filter.id, [subfilter]]]), }]])); } else { const indexEntry = field.get(arrayIndex); if (indexEntry) { indexEntry.engine.store(filter); const subfilters = indexEntry.filters.get(filter.id); if (subfilters) { subfilters.push(subfilter); } else { indexEntry.filters.set(filter.id, [subfilter]); } } else { const engine = new this.Engine(this.config); engine.store(filter); field.set(arrayIndex, { engine, filters: new Map([[filter.id, [subfilter]]]), }); } } } /** * Stores a "match" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ match(operand, subfilter, condition) { const filters = operand.custom.filters; if (!filters) { operand.custom.filters = [{ subfilter, value: condition.value, }]; } else { filters.push({ subfilter, value: condition.value }); } } /** * Stores a "not match" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ notmatch(operand, subfilter, condition) { this.match(operand, subfilter, condition); } /** * Stores a "not equals" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ notequals(operand, subfilter, condition) { this.equals(operand, subfilter, condition); } /** * Stores a "exists" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ exists(operand, subfilter, condition) { const { path, value } = condition.value; let field = operand.fields.get(path); if (!field) { field = { subfilters: new Set(), values: new Map() }; operand.fields.set(path, field); } if (!condition.value.array) { field.subfilters.add(subfilter); } else { const entries = field.values.get(value); if (entries !== undefined) { entries.add(subfilter); } else { field.values.set(value, new Set([subfilter])); } } } /** * Stores a "not exists" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ notexists(operand, subfilter, condition) { this.exists(operand, subfilter, condition); } nothing(operand, subfilter) { operand.fields.set('all', [subfilter]); } /** * Stores a "range" condition into the field-operand structure * * Stores the range in interval trees for searches in O(log n + m) * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ range(operand, subfilter, condition) { const fieldName = Object.keys(condition.value)[0]; const rangeCondition = new rangeCondition_1.RangeCondition(subfilter, condition); let field = operand.fields.get(fieldName); let entry; if (!field) { field = { conditions: new Map(), tree: new interval_tree_1.default(), }; operand.fields.set(fieldName, field); } else { entry = field.conditions.get(condition.id); } if (entry !== undefined) { entry.subfilters.add(subfilter); } else { field.conditions.set(condition.id, rangeCondition); field.tree.insert([rangeCondition.low, rangeCondition.high], rangeCondition); } } /** * Stores a "not range" condition into the field-operand structure * * "not range" conditions are stored as an inverted range, * meaning that if a user subscribes to the following range: * [min, max] * Then we register the following ranges in the tree: * ]-Infinity, min[ * ]max, +Infinity[ * * (boundaries are also reversed: inclusive boundaries become * exclusive, and vice-versa) * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ notrange(operand, subfilter, condition) { const fieldName = Object.keys(condition.value)[0]; const rangeCondition = new rangeCondition_1.RangeCondition(subfilter, condition); let field = operand.fields.get(fieldName); let entry; if (!field) { field = { conditions: new Map(), tree: new interval_tree_1.default(), }; operand.fields.set(fieldName, field); } else { entry = field.conditions.get(condition.id); } if (entry !== undefined) { entry.subfilters.add(subfilter); } else { field.conditions.set(condition.id, rangeCondition); if (rangeCondition.low !== -Infinity) { field.tree.insert([-Infinity, rangeCondition.low], rangeCondition); } if (rangeCondition.high !== Infinity) { field.tree.insert([rangeCondition.high, Infinity], rangeCondition); } } } /** * Stores a "regexp" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ regexp(operand, subfilter, condition) { const fieldName = Object.keys(condition.value)[0]; const value = new regexpCondition_1.RegExpCondition(this.config, condition.value[fieldName].value, subfilter, condition.value[fieldName].flags); let field = operand.fields.get(fieldName); if (!field) { field = new Map(); operand.fields.set(fieldName, field); } const entry = field.get(value.stringValue); if (entry !== undefined) { entry.subfilters.add(subfilter); } else { field.set(value.stringValue, value); } } /** * Stores a "not regexp" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ notregexp(operand, subfilter, condition) { this.regexp(operand, subfilter, condition); } /** * Stores a "geospatial" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ geospatial(operand, subfilter, condition) { const geotype = Object.keys(condition.value)[0]; const fieldName = Object.keys(condition.value[geotype])[0]; const value = condition.value[geotype][fieldName]; if (!operand.custom.index) { operand.custom.index = new BoostSpatialIndex(); } let field = operand.fields.get(fieldName); if (field === undefined) { field = new Map(); operand.fields.set(fieldName, field); } if (field.has(condition.id)) { field.get(condition.id).add(subfilter); } else { field.set(condition.id, new Set([subfilter])); storeGeoshape(operand.custom.index, geotype, condition.id, value); } } /** * Stores a "not geospatial" condition into the field-operand structure * * @param {FieldOperand} operand * @param {object} subfilter * @param {object} condition */ notgeospatial(operand, subfilter, condition) { this.geospatial(operand, subfilter, condition); } } exports.OperandsStorage = OperandsStorage; /** * Stores a geospatial shape in the provided index object. * * @param {object} index * @param {string} type * @param {string} id * @param {Object|Array} shape */ function storeGeoshape(index, type, id, shape) { switch (type) { case 'geoBoundingBox': index.addBoundingBox(id, shape.bottom, shape.left, shape.top, shape.right); break; case 'geoDistance': index.addCircle(id, shape.lat, shape.lon, shape.distance); break; case 'geoDistanceRange': index.addAnnulus(id, shape.lat, shape.lon, shape.to, shape.from); break; case 'geoPolygon': index.addPolygon(id, shape); break; default: break; } } //# sourceMappingURL=storeOperands.js.map