Add bounded live query cursor continuation
This commit is contained in:
@@ -382,12 +382,20 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
|
||||
"Local and replica queries must use the same root contract",
|
||||
);
|
||||
const traversals = (linked: LinkedQuery) => {
|
||||
const paths = new Map<string, boolean>();
|
||||
const paths = new Map<string, { complete: boolean; requiresComplete: boolean; windows: Set<string> }>();
|
||||
const entry = (key: string) => {
|
||||
let value = paths.get(key);
|
||||
if (!value) {
|
||||
value = { complete: false, requiresComplete: false, windows: new Set() };
|
||||
paths.set(key, value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const visit = (selections: LinkedQuery["checked"]["selection"], parents: string[]) => {
|
||||
for (const selection of selections) {
|
||||
for (const stage of selection.relational?.stages ?? []) {
|
||||
if (stage.operation.kind === "source")
|
||||
paths.set(JSON.stringify([...parents, stage.operation.memberId]), false);
|
||||
entry(JSON.stringify([...parents, stage.operation.memberId])).requiresComplete = true;
|
||||
}
|
||||
const member = selection.interfaceRevisionId
|
||||
? interfaces.get(selection.interfaceRevisionId)?.members.find((m) => m.id === selection.memberId)
|
||||
@@ -397,7 +405,11 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
|
||||
const many = member.cardinality === "many" || member.cardinality === "many-unique";
|
||||
const complete = !selection.predicate && (!many || !!selection.arguments.all);
|
||||
const key = JSON.stringify(path);
|
||||
paths.set(key, complete || paths.get(key) === true);
|
||||
const value = entry(key);
|
||||
value.complete ||= complete;
|
||||
if (selection.arguments.first)
|
||||
value.windows.add(canonicalJson({ arguments: selection.arguments, conditions: selection.conditions }));
|
||||
else value.requiresComplete = true;
|
||||
}
|
||||
visit(selection.selection, path);
|
||||
}
|
||||
@@ -406,18 +418,23 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
|
||||
return paths;
|
||||
};
|
||||
const admittedTraversals = traversals(source.query);
|
||||
for (const traversal of traversals(query.query).keys()) {
|
||||
for (const [traversal, needed] of traversals(query.query)) {
|
||||
if (!admittedTraversals.has(traversal))
|
||||
throw new ExecutionPlanError(
|
||||
"REPLICA_TRAVERSAL_MISSING",
|
||||
query.id,
|
||||
`Replica source does not admit relationship path ${traversal}`,
|
||||
);
|
||||
if (!admittedTraversals.get(traversal))
|
||||
if (
|
||||
!admittedTraversals.get(traversal)!.complete &&
|
||||
(needed.requiresComplete ||
|
||||
!needed.windows.size ||
|
||||
[...needed.windows].some((window) => !admittedTraversals.get(traversal)!.windows.has(window)))
|
||||
)
|
||||
throw new ExecutionPlanError(
|
||||
"REPLICA_RELATION_INCOMPLETE",
|
||||
query.id,
|
||||
`Local relationship ${traversal} needs an unfiltered bounded all selection in its replica source; a filtered/windowed source cannot supply unseen matches`,
|
||||
`Local relationship ${traversal} needs either complete source candidates or the identical confirmed first window; a window cannot supply unseen matches`,
|
||||
);
|
||||
}
|
||||
// The admission executor follows these canonical dependencies on the admitted roots.
|
||||
|
||||
@@ -22,8 +22,6 @@ export function unsupportedQueryOperator(query: CheckedQuery): string | undefine
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user