Add bounded live query cursor continuation

This commit is contained in:
Timothy J. Aveni
2026-09-22 17:20:00 -07:00
parent b8571d054b
commit ca83dbd1a7
4 changed files with 51 additions and 8 deletions
+23 -6
View File
@@ -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.