@tanstack/db
Version:
A reactive client store for building super fast apps on sync
374 lines (373 loc) • 13.2 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const utils = require("../utils.cjs");
const reverseIndex = require("../indexes/reverse-index.cjs");
const virtualProps = require("../virtual-props.cjs");
const comparison = require("./comparison.cjs");
function findIndexForField(collection, fieldPath, compareOptions) {
if (virtualProps.hasVirtualPropPath(fieldPath)) {
return void 0;
}
const compareOpts = compareOptions ?? {
...utils.DEFAULT_COMPARE_OPTIONS,
...collection.compareOptions
};
for (const index of collection.indexes.values()) {
if (index.matchesField(fieldPath) && index.matchesCompareOptions(compareOpts)) {
if (!index.matchesDirection(compareOpts.direction)) {
return new reverseIndex.ReverseIndex(index);
}
return index;
}
}
return void 0;
}
function intersectSets(sets) {
if (sets.length === 0) return /* @__PURE__ */ new Set();
if (sets.length === 1) return new Set(sets[0]);
let result = new Set(sets[0]);
for (let i = 1; i < sets.length; i++) {
const newResult = /* @__PURE__ */ new Set();
for (const item of result) {
if (sets[i].has(item)) {
newResult.add(item);
}
}
result = newResult;
}
return result;
}
function unionSets(sets) {
const result = /* @__PURE__ */ new Set();
for (const set of sets) {
for (const item of set) {
result.add(item);
}
}
return result;
}
function isExactComparisonValue(value) {
return value != null;
}
function usesLocaleStringSort(collection) {
const opts = { ...utils.DEFAULT_COMPARE_OPTIONS, ...collection.compareOptions };
return opts.stringSort === `locale`;
}
function isRangeOrderingDivergent(value, collection) {
switch (typeof value) {
case `number`:
case `bigint`:
case `boolean`:
return false;
case `string`:
return usesLocaleStringSort(collection);
case `object`: {
if (value === null) return false;
return !(value instanceof Date);
}
default:
return false;
}
}
function canRangeOptimize(value, index, collection) {
return !isRangeOrderingDivergent(value, collection) && index.supportsRangeOptimization;
}
function optimizeExpressionWithIndexes(expression, collection) {
return optimizeQueryRecursive(expression, collection);
}
function optimizeQueryRecursive(expression, collection) {
if (expression.type === `func`) {
switch (expression.name) {
case `eq`:
case `gt`:
case `gte`:
case `lt`:
case `lte`:
return optimizeSimpleComparison(expression, collection);
case `and`:
return optimizeAndExpression(expression, collection);
case `or`:
return optimizeOrExpression(expression, collection);
case `in`:
return optimizeInArrayExpression(expression, collection);
}
}
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
function optimizeCompoundRangeQuery(expression, collection) {
if (expression.type !== `func` || expression.args.length < 2) {
return {
canOptimize: false,
matchingKeys: /* @__PURE__ */ new Set(),
isExact: false,
coveredArgIndices: /* @__PURE__ */ new Set()
};
}
const fieldOperations = /* @__PURE__ */ new Map();
for (const [argIndex, arg] of expression.args.entries()) {
if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {
const rangeOp = arg;
if (rangeOp.args.length === 2) {
const leftArg = rangeOp.args[0];
const rightArg = rangeOp.args[1];
let fieldArg = null;
let valueArg = null;
let operation = rangeOp.name;
if (leftArg.type === `ref` && rightArg.type === `val`) {
fieldArg = leftArg;
valueArg = rightArg;
} else if (leftArg.type === `val` && rightArg.type === `ref`) {
fieldArg = rightArg;
valueArg = leftArg;
switch (operation) {
case `gt`:
operation = `lt`;
break;
case `gte`:
operation = `lte`;
break;
case `lt`:
operation = `gt`;
break;
case `lte`:
operation = `gte`;
break;
}
}
if (fieldArg && valueArg) {
const fieldPath = fieldArg.path;
const fieldKey = fieldPath.join(`.`);
const value = valueArg.value;
if (!fieldOperations.has(fieldKey)) {
fieldOperations.set(fieldKey, []);
}
fieldOperations.get(fieldKey).push({ operation, value, argIndex });
}
}
}
}
for (const [fieldKey, operations] of fieldOperations) {
if (operations.length >= 2) {
const fieldPath = fieldKey.split(`.`);
const index = findIndexForField(collection, fieldPath);
if (index && operations.some((op) => !canRangeOptimize(op.value, index, collection))) {
continue;
}
if (index && index.supports(`gt`) && index.supports(`lt`)) {
const compare = comparison.makeComparator({
...utils.DEFAULT_COMPARE_OPTIONS,
...collection.compareOptions,
direction: `asc`
});
let from = void 0;
let to = void 0;
let hasFromBound = false;
let hasToBound = false;
let fromInclusive = true;
let toInclusive = true;
let hasNonComparableBound = false;
for (const { operation, value } of operations) {
if (!isExactComparisonValue(value)) {
hasNonComparableBound = true;
continue;
}
switch (operation) {
case `gt`:
case `gte`: {
const cmp = hasFromBound ? compare(value, from) : 1;
if (cmp > 0) {
from = value;
hasFromBound = true;
fromInclusive = operation === `gte`;
} else if (cmp === 0 && operation === `gt`) {
fromInclusive = false;
}
break;
}
case `lt`:
case `lte`: {
const cmp = hasToBound ? compare(value, to) : -1;
if (cmp < 0) {
to = value;
hasToBound = true;
toInclusive = operation === `lte`;
} else if (cmp === 0 && operation === `lt`) {
toInclusive = false;
}
break;
}
}
}
const rangeOptions = {};
if (hasFromBound) {
rangeOptions.from = from;
rangeOptions.fromInclusive = fromInclusive;
}
if (hasToBound) {
rangeOptions.to = to;
rangeOptions.toInclusive = toInclusive;
}
const matchingKeys = index.rangeQuery(rangeOptions);
return {
canOptimize: true,
matchingKeys,
// The range result is exact only when it cannot include rows with a
// nullish indexed value (which a comparison would reject but the
// index returns, as they sort as the smallest key). That requires a
// non-nullish lower bound to exclude them: without `hasFromBound`
// the range is open at the bottom and captures those rows, and a
// non-comparable bound value (`hasNonComparableBound`) can never
// bound them out.
isExact: hasFromBound && !hasNonComparableBound,
coveredArgIndices: new Set(operations.map((op) => op.argIndex))
};
}
}
}
return {
canOptimize: false,
matchingKeys: /* @__PURE__ */ new Set(),
isExact: false,
coveredArgIndices: /* @__PURE__ */ new Set()
};
}
function optimizeSimpleComparison(expression, collection) {
if (expression.type !== `func` || expression.args.length !== 2) {
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
const leftArg = expression.args[0];
const rightArg = expression.args[1];
let fieldArg = null;
let valueArg = null;
let operation = expression.name;
if (leftArg.type === `ref` && rightArg.type === `val`) {
fieldArg = leftArg;
valueArg = rightArg;
} else if (leftArg.type === `val` && rightArg.type === `ref`) {
fieldArg = rightArg;
valueArg = leftArg;
switch (operation) {
case `gt`:
operation = `lt`;
break;
case `gte`:
operation = `lte`;
break;
case `lt`:
operation = `gt`;
break;
case `lte`:
operation = `gte`;
break;
}
}
if (fieldArg && valueArg) {
const fieldPath = fieldArg.path;
const index = findIndexForField(collection, fieldPath);
if (index) {
const queryValue = valueArg.value;
const indexOperation = operation;
if (!index.supports(indexOperation)) {
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
if ((operation === `gt` || operation === `gte` || operation === `lt` || operation === `lte`) && !canRangeOptimize(queryValue, index, collection)) {
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
const matchingKeys = index.lookup(indexOperation, queryValue);
const isExact = operation === `lt` || operation === `lte` ? false : isExactComparisonValue(queryValue);
return { canOptimize: true, matchingKeys, isExact };
}
}
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
function optimizeAndExpression(expression, collection) {
if (expression.type !== `func` || expression.args.length < 2) {
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
const compoundRangeResult = optimizeCompoundRangeQuery(expression, collection);
const coveredArgIndices = compoundRangeResult.canOptimize ? compoundRangeResult.coveredArgIndices : /* @__PURE__ */ new Set();
const results = [];
if (compoundRangeResult.canOptimize) {
results.push(compoundRangeResult);
}
let allConjunctsExact = !compoundRangeResult.canOptimize ? true : compoundRangeResult.isExact;
for (const [argIndex, arg] of expression.args.entries()) {
if (coveredArgIndices.has(argIndex)) {
continue;
}
const result = optimizeQueryRecursive(arg, collection);
if (result.canOptimize) {
results.push(result);
if (!result.isExact) {
allConjunctsExact = false;
}
} else {
allConjunctsExact = false;
}
}
if (results.length > 0) {
const allMatchingSets = results.map((r) => r.matchingKeys);
const intersectedKeys = intersectSets(allMatchingSets);
return {
canOptimize: true,
matchingKeys: intersectedKeys,
isExact: allConjunctsExact
};
}
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
function optimizeOrExpression(expression, collection) {
if (expression.type !== `func` || expression.args.length < 2) {
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
const results = [];
for (const arg of expression.args) {
const result = optimizeQueryRecursive(arg, collection);
if (!result.canOptimize) {
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
results.push(result);
}
const allMatchingSets = results.map((r) => r.matchingKeys);
const unionedKeys = unionSets(allMatchingSets);
return {
canOptimize: true,
matchingKeys: unionedKeys,
// An inexact (superset) disjunct makes the union a superset as well
isExact: results.every((r) => r.isExact)
};
}
function optimizeInArrayExpression(expression, collection) {
if (expression.type !== `func` || expression.args.length !== 2) {
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
const fieldArg = expression.args[0];
const arrayArg = expression.args[1];
if (fieldArg.type === `ref` && arrayArg.type === `val` && Array.isArray(arrayArg.value)) {
const fieldPath = fieldArg.path;
const values = arrayArg.value;
const index = findIndexForField(collection, fieldPath);
const isExact = values.every((value) => isExactComparisonValue(value));
if (index) {
if (index.supports(`in`)) {
const matchingKeys = index.lookup(`in`, values);
return { canOptimize: true, matchingKeys, isExact };
} else if (index.supports(`eq`)) {
const matchingKeys = /* @__PURE__ */ new Set();
for (const value of values) {
const keysForValue = index.lookup(`eq`, value);
for (const key of keysForValue) {
matchingKeys.add(key);
}
}
return { canOptimize: true, matchingKeys, isExact };
}
}
}
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set(), isExact: false };
}
exports.findIndexForField = findIndexForField;
exports.intersectSets = intersectSets;
exports.optimizeExpressionWithIndexes = optimizeExpressionWithIndexes;
exports.unionSets = unionSets;
//# sourceMappingURL=index-optimization.cjs.map