63 lines
3.0 KiB
TypeScript
63 lines
3.0 KiB
TypeScript
import type { CheckedQuery, QuerySelection } from "../query/types.js";
|
|
import type { QueryPredicate, QueryPathStep } from "../query/relational.js";
|
|
|
|
/** Supported by the fresh coordinator/replica interpreters. The language parser
|
|
* can describe more operators; that does not make them executable in this world. */
|
|
export function unsupportedQueryOperator(query: CheckedQuery): string | undefined {
|
|
const path = (steps: QueryPathStep[]) =>
|
|
steps.some((step) => step.kind !== "relation") ? "expanded source/target paths" : undefined;
|
|
const predicate = (value: QueryPredicate): string | undefined => {
|
|
if (value.kind === "reduce") return "aggregate predicates";
|
|
if (value.kind === "and" || value.kind === "or") return value.children.map(predicate).find(Boolean);
|
|
if (value.kind === "not") return predicate(value.child);
|
|
if (value.kind === "compare")
|
|
return (
|
|
path(value.expression.path) ??
|
|
(value.expression.leaf.kind === "mapKey" ? "keyed relationship operands" : undefined)
|
|
);
|
|
if (value.kind === "relation") return path(value.path) ?? (value.predicate && predicate(value.predicate));
|
|
return undefined;
|
|
};
|
|
const conditional = (selections: readonly QuerySelection[]): boolean =>
|
|
selections.some((selection) => selection.conditions.length > 0 || conditional(selection.selection));
|
|
const walk = (selections: readonly QuerySelection[]): string | undefined => {
|
|
for (const selection of selections) {
|
|
const after = selection.arguments.after;
|
|
if (after && !(after.kind === "literal" && after.value === null)) return "cursor continuation";
|
|
if (selection.predicate) {
|
|
const reason = predicate(selection.predicate);
|
|
if (reason) return reason;
|
|
}
|
|
if (selection.relational) {
|
|
if (query.declaration.placement?.kind === "replica") return "aggregate selections in canonical-input scopes";
|
|
const { stages, terminals } = selection.relational;
|
|
if (stages.length !== 1 || stages[0]!.operation.kind !== "source")
|
|
return "relational expansion, filtering or distinct stages";
|
|
if (terminals.length !== 1) return "multiple aggregate terminals";
|
|
const terminal = terminals[0]!;
|
|
if (
|
|
terminal.kind !== "aggregate" ||
|
|
terminal.stage !== stages[0]!.id ||
|
|
terminal.keys.length ||
|
|
terminal.order.length ||
|
|
terminal.residual ||
|
|
terminal.where ||
|
|
terminal.having ||
|
|
terminal.first ||
|
|
terminal.all ||
|
|
terminal.after ||
|
|
terminal.reductions.some(
|
|
(reduction) => reduction.reduction.operator !== "count" || reduction.reduction.operand,
|
|
)
|
|
)
|
|
return "grouping or reductions other than a direct relationship count";
|
|
if (conditional(selection.selection)) return "conditional aggregate projections";
|
|
}
|
|
const reason = walk(selection.selection);
|
|
if (reason) return reason;
|
|
}
|
|
return undefined;
|
|
};
|
|
return walk(query.selection);
|
|
}
|