@tanstack/db
Version:
A reactive client store for building super fast apps on sync
174 lines (173 loc) • 5.89 kB
JavaScript
import { map, join, consolidate, filter } from "@tanstack/db-ivm";
import { UnsupportedJoinTypeError, UnsupportedJoinSourceTypeError, CollectionInputNotFoundError, InvalidJoinConditionSameTableError, InvalidJoinConditionTableMismatchError, InvalidJoinConditionWrongTablesError } from "../../errors.js";
import { compileExpression } from "./evaluators.js";
import { compileQuery } from "./index.js";
function processJoins(pipeline, joinClauses, tables, mainTableAlias, allInputs, cache, queryMapping) {
let resultPipeline = pipeline;
for (const joinClause of joinClauses) {
resultPipeline = processJoin(
resultPipeline,
joinClause,
tables,
mainTableAlias,
allInputs,
cache,
queryMapping
);
}
return resultPipeline;
}
function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, cache, queryMapping) {
const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(
joinClause.from,
allInputs,
cache,
queryMapping
);
tables[joinedTableAlias] = joinedInput;
const joinType = joinClause.type === `cross` ? `inner` : joinClause.type === `outer` ? `full` : joinClause.type;
const { mainExpr, joinedExpr } = analyzeJoinExpressions(
joinClause.left,
joinClause.right,
mainTableAlias,
joinedTableAlias
);
const compiledMainExpr = compileExpression(mainExpr);
const compiledJoinedExpr = compileExpression(joinedExpr);
const mainPipeline = pipeline.pipe(
map(([currentKey, namespacedRow]) => {
const mainKey = compiledMainExpr(namespacedRow);
return [mainKey, [currentKey, namespacedRow]];
})
);
const joinedPipeline = joinedInput.pipe(
map(([currentKey, row]) => {
const namespacedRow = { [joinedTableAlias]: row };
const joinedKey = compiledJoinedExpr(namespacedRow);
return [joinedKey, [currentKey, namespacedRow]];
})
);
if (![`inner`, `left`, `right`, `full`].includes(joinType)) {
throw new UnsupportedJoinTypeError(joinClause.type);
}
return mainPipeline.pipe(
join(joinedPipeline, joinType),
consolidate(),
processJoinResults(joinClause.type)
);
}
function analyzeJoinExpressions(left, right, mainTableAlias, joinedTableAlias) {
const leftTableAlias = getTableAliasFromExpression(left);
const rightTableAlias = getTableAliasFromExpression(right);
if (leftTableAlias === mainTableAlias && rightTableAlias === joinedTableAlias) {
return { mainExpr: left, joinedExpr: right };
}
if (leftTableAlias === joinedTableAlias && rightTableAlias === mainTableAlias) {
return { mainExpr: right, joinedExpr: left };
}
if (leftTableAlias === rightTableAlias) {
throw new InvalidJoinConditionSameTableError(leftTableAlias || `unknown`);
}
if (!leftTableAlias || !rightTableAlias) {
throw new InvalidJoinConditionTableMismatchError(
mainTableAlias,
joinedTableAlias
);
}
throw new InvalidJoinConditionWrongTablesError(
leftTableAlias,
rightTableAlias,
mainTableAlias,
joinedTableAlias
);
}
function getTableAliasFromExpression(expr) {
switch (expr.type) {
case `ref`:
return expr.path[0] || null;
case `func`: {
const tableAliases = /* @__PURE__ */ new Set();
for (const arg of expr.args) {
const alias = getTableAliasFromExpression(arg);
if (alias) {
tableAliases.add(alias);
}
}
return tableAliases.size === 1 ? Array.from(tableAliases)[0] : null;
}
default:
return null;
}
}
function processJoinSource(from, allInputs, cache, queryMapping) {
switch (from.type) {
case `collectionRef`: {
const input = allInputs[from.collection.id];
if (!input) {
throw new 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(
map((data) => {
const [key, [value, _orderByIndex]] = data;
return [key, value];
})
);
return { alias: from.alias, input: extractedInput };
}
default:
throw new UnsupportedJoinSourceTypeError(from.type);
}
}
function processJoinResults(joinType) {
return function(pipeline) {
return pipeline.pipe(
// Process the join result and handle nulls
filter((result) => {
const [_key, [main, joined]] = result;
const mainNamespacedRow = main == null ? void 0 : main[1];
const joinedNamespacedRow = joined == null ? void 0 : joined[1];
if (joinType === `inner`) {
return !!(mainNamespacedRow && joinedNamespacedRow);
}
if (joinType === `left`) {
return !!mainNamespacedRow;
}
if (joinType === `right`) {
return !!joinedNamespacedRow;
}
return true;
}),
map((result) => {
const [_key, [main, joined]] = result;
const mainKey = main == null ? void 0 : main[0];
const mainNamespacedRow = main == null ? void 0 : main[1];
const joinedKey = joined == null ? void 0 : joined[0];
const joinedNamespacedRow = joined == null ? void 0 : joined[1];
const mergedNamespacedRow = {};
if (mainNamespacedRow) {
Object.assign(mergedNamespacedRow, mainNamespacedRow);
}
if (joinedNamespacedRow) {
Object.assign(mergedNamespacedRow, joinedNamespacedRow);
}
const resultKey = `[${mainKey},${joinedKey}]`;
return [resultKey, mergedNamespacedRow];
})
);
};
}
export {
processJoins
};
//# sourceMappingURL=joins.js.map