@tanstack/db
Version:
A reactive client store for building super fast apps on sync
230 lines (229 loc) • 7.12 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const dbIvm = require("@tanstack/db-ivm");
const optimizer = require("../optimizer.cjs");
const errors = require("../../errors.cjs");
const evaluators = require("./evaluators.cjs");
const joins = require("./joins.cjs");
const groupBy = require("./group-by.cjs");
const orderBy = require("./order-by.cjs");
const select = require("./select.cjs");
function compileQuery(rawQuery, inputs, cache = /* @__PURE__ */ new WeakMap(), queryMapping = /* @__PURE__ */ new WeakMap()) {
const cachedResult = cache.get(rawQuery);
if (cachedResult) {
return cachedResult;
}
const { optimizedQuery: query, collectionWhereClauses } = optimizer.optimizeQuery(rawQuery);
queryMapping.set(query, rawQuery);
mapNestedQueries(query, rawQuery, queryMapping);
const allInputs = { ...inputs };
const tables = {};
const { alias: mainTableAlias, input: mainInput } = processFrom(
query.from,
allInputs,
cache,
queryMapping
);
tables[mainTableAlias] = mainInput;
let pipeline = mainInput.pipe(
dbIvm.map(([key, row]) => {
const ret = [key, { [mainTableAlias]: row }];
return ret;
})
);
if (query.join && query.join.length > 0) {
pipeline = joins.processJoins(
pipeline,
query.join,
tables,
mainTableAlias,
allInputs,
cache,
queryMapping
);
}
if (query.where && query.where.length > 0) {
for (const where of query.where) {
const compiledWhere = evaluators.compileExpression(where);
pipeline = pipeline.pipe(
dbIvm.filter(([_key, namespacedRow]) => {
return compiledWhere(namespacedRow);
})
);
}
}
if (query.fnWhere && query.fnWhere.length > 0) {
for (const fnWhere of query.fnWhere) {
pipeline = pipeline.pipe(
dbIvm.filter(([_key, namespacedRow]) => {
return fnWhere(namespacedRow);
})
);
}
}
if (query.distinct && !query.fnSelect && !query.select) {
throw new errors.DistinctRequiresSelectError();
}
if (query.fnSelect) {
pipeline = pipeline.pipe(
dbIvm.map(([key, namespacedRow]) => {
const selectResults = query.fnSelect(namespacedRow);
return [
key,
{
...namespacedRow,
__select_results: selectResults
}
];
})
);
} else if (query.select) {
pipeline = select.processSelectToResults(pipeline, query.select);
} else {
pipeline = pipeline.pipe(
dbIvm.map(([key, namespacedRow]) => {
const selectResults = !query.join && !query.groupBy ? namespacedRow[mainTableAlias] : namespacedRow;
return [
key,
{
...namespacedRow,
__select_results: selectResults
}
];
})
);
}
if (query.groupBy && query.groupBy.length > 0) {
pipeline = groupBy.processGroupBy(
pipeline,
query.groupBy,
query.having,
query.select,
query.fnHaving
);
} else if (query.select) {
const hasAggregates = Object.values(query.select).some(
(expr) => expr.type === `agg`
);
if (hasAggregates) {
pipeline = groupBy.processGroupBy(
pipeline,
[],
// Empty group by means single group
query.having,
query.select,
query.fnHaving
);
}
}
if (query.having && (!query.groupBy || query.groupBy.length === 0)) {
const hasAggregates = query.select ? Object.values(query.select).some((expr) => expr.type === `agg`) : false;
if (!hasAggregates) {
throw new errors.HavingRequiresGroupByError();
}
}
if (query.fnHaving && query.fnHaving.length > 0 && (!query.groupBy || query.groupBy.length === 0)) {
for (const fnHaving of query.fnHaving) {
pipeline = pipeline.pipe(
dbIvm.filter(([_key, namespacedRow]) => {
return fnHaving(namespacedRow);
})
);
}
}
if (query.distinct) {
pipeline = pipeline.pipe(dbIvm.distinct(([_key, row]) => row.__select_results));
}
if (query.orderBy && query.orderBy.length > 0) {
const orderedPipeline = orderBy.processOrderBy(
pipeline,
query.orderBy,
query.limit,
query.offset
);
const resultPipeline2 = orderedPipeline.pipe(
dbIvm.map(([key, [row, orderByIndex]]) => {
const finalResults = row.__select_results;
return [key, [finalResults, orderByIndex]];
})
);
const result2 = resultPipeline2;
const compilationResult2 = {
pipeline: result2,
collectionWhereClauses
};
cache.set(rawQuery, compilationResult2);
return compilationResult2;
} else if (query.limit !== void 0 || query.offset !== void 0) {
throw new errors.LimitOffsetRequireOrderByError();
}
const resultPipeline = pipeline.pipe(
dbIvm.map(([key, row]) => {
const finalResults = row.__select_results;
return [key, [finalResults, void 0]];
})
);
const result = resultPipeline;
const compilationResult = {
pipeline: result,
collectionWhereClauses
};
cache.set(rawQuery, compilationResult);
return compilationResult;
}
function processFrom(from, allInputs, cache, queryMapping) {
switch (from.type) {
case `collectionRef`: {
const input = allInputs[from.collection.id];
if (!input) {
throw new errors.CollectionInputNotFoundError(from.collection.id);
}
return { alias: from.alias, input };
}
case `queryRef`: {
const originalQuery = queryMapping.get(from.query) || from.query;
const subQueryResult = compileQuery(
originalQuery,
allInputs,
cache,
queryMapping
);
const subQueryInput = subQueryResult.pipeline;
const extractedInput = subQueryInput.pipe(
dbIvm.map((data) => {
const [key, [value, _orderByIndex]] = data;
return [key, value];
})
);
return { alias: from.alias, input: extractedInput };
}
default:
throw new errors.UnsupportedFromTypeError(from.type);
}
}
function mapNestedQueries(optimizedQuery, originalQuery, queryMapping) {
if (optimizedQuery.from.type === `queryRef` && originalQuery.from.type === `queryRef`) {
queryMapping.set(optimizedQuery.from.query, originalQuery.from.query);
mapNestedQueries(
optimizedQuery.from.query,
originalQuery.from.query,
queryMapping
);
}
if (optimizedQuery.join && originalQuery.join) {
for (let i = 0; i < optimizedQuery.join.length && i < originalQuery.join.length; i++) {
const optimizedJoin = optimizedQuery.join[i];
const originalJoin = originalQuery.join[i];
if (optimizedJoin.from.type === `queryRef` && originalJoin.from.type === `queryRef`) {
queryMapping.set(optimizedJoin.from.query, originalJoin.from.query);
mapNestedQueries(
optimizedJoin.from.query,
originalJoin.from.query,
queryMapping
);
}
}
}
}
exports.compileQuery = compileQuery;
//# sourceMappingURL=index.cjs.map