nodedb-json
Version:
A lightweight JSON-based database for Node.js with TypeScript support, indexing, and complex query capabilities
138 lines • 5.59 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryEngine = void 0;
const lodash_1 = __importDefault(require("lodash"));
const aggregator_1 = require("./aggregator");
const matcher_1 = require("./matcher");
const paginator_1 = require("./paginator");
const sorter_1 = require("./sorter");
class QueryEngine {
constructor(indexes) {
this.indexes = indexes;
}
execute(data, options = {}) {
const startTime = performance.now();
this.validateQueryOptions(options);
const totalRecords = data.length;
let filteredData = [...data];
let usedIndex = false;
if (options.where) {
const filterResult = this.applyFilter(filteredData, options.where);
filteredData = filterResult.data;
usedIndex = filterResult.usedIndex;
}
const filteredRecords = filteredData.length;
const aggregationData = filteredData;
if (options.sort) {
filteredData = (0, sorter_1.applySort)(filteredData, options.sort);
}
if (options.skip && options.skip > 0) {
filteredData = filteredData.slice(options.skip);
}
if (options.limit && options.limit > 0) {
filteredData = filteredData.slice(0, options.limit);
}
let paginationInfo;
if (options.pagination) {
const paginationResult = (0, paginator_1.applyPagination)(filteredData, options.pagination);
filteredData = paginationResult.data;
paginationInfo = paginationResult.pagination;
}
if (options.select && options.select.length > 0) {
filteredData = this.applySelect(filteredData, options.select);
}
let aggregations;
if (options.aggregation && options.aggregation.length > 0) {
aggregations = (0, aggregator_1.applyAggregation)(aggregationData, options.aggregation);
}
const executionTime = performance.now() - startTime;
return {
data: lodash_1.default.cloneDeep(filteredData),
pagination: paginationInfo,
aggregations: lodash_1.default.cloneDeep(aggregations),
stats: {
totalRecords,
filteredRecords,
executionTime,
usedIndex
}
};
}
validateQueryOptions(options) {
if (options.skip !== undefined && (!Number.isInteger(options.skip) || options.skip < 0)) {
throw new Error('Query skip must be a non-negative integer');
}
if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1)) {
throw new Error('Query limit must be a positive integer');
}
if (options.pagination) {
(0, paginator_1.validatePagination)(options.pagination);
}
}
applyFilter(data, where) {
if (typeof where === 'function') {
return { data: data.filter(where), usedIndex: false };
}
if (this.indexes) {
const indexedMatches = this.getIndexedCandidateIndexes(where);
if (indexedMatches) {
const filteredData = indexedMatches
.map(index => data[index])
.filter(item => item !== undefined && (0, matcher_1.matchesConditions)(item, where));
return { data: filteredData, usedIndex: true };
}
}
const filteredData = data.filter(item => (0, matcher_1.matchesConditions)(item, where));
return { data: filteredData, usedIndex: false };
}
getIndexedCandidateIndexes(where) {
var _a;
const indexSets = [];
for (const [field, condition] of Object.entries(where)) {
if (!((_a = this.indexes) === null || _a === void 0 ? void 0 : _a.hasIndexOnField(field))) {
continue;
}
const values = this.getIndexLookupValues(condition);
if (!values) {
continue;
}
const indexes = lodash_1.default.uniq(values.flatMap(value => this.indexes.getItemIndexesByField(field, value)));
indexSets.push(indexes);
}
if (indexSets.length === 0) {
return undefined;
}
return indexSets
.sort((left, right) => left.length - right.length)
.reduce((result, indexes) => result.filter(index => indexes.includes(index)))
.sort((left, right) => left - right);
}
getIndexLookupValues(condition) {
if (!this.isOperatorObject(condition)) {
return [condition];
}
const operators = condition;
if (Object.prototype.hasOwnProperty.call(operators, '$eq')) {
return [operators.$eq];
}
if (Object.prototype.hasOwnProperty.call(operators, '$in')) {
return Array.isArray(operators.$in) ? operators.$in : undefined;
}
return undefined;
}
isOperatorObject(value) {
return !!value &&
typeof value === 'object' &&
!Array.isArray(value) &&
!(value instanceof Date) &&
Object.keys(value).some(key => key.startsWith('$'));
}
applySelect(data, select) {
return data.map(item => lodash_1.default.pick(item, select));
}
}
exports.QueryEngine = QueryEngine;
//# sourceMappingURL=query-engine.js.map