@rocicorp/zero 1.11.0-canary.0 → 1.11.0-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/replicache/src/btree/read.d.ts +12 -1
- package/out/replicache/src/btree/read.d.ts.map +1 -1
- package/out/replicache/src/btree/read.js +24 -12
- package/out/replicache/src/btree/read.js.map +1 -1
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +10 -0
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/transactions.d.ts.map +1 -1
- package/out/replicache/src/transactions.js +1 -1
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +17 -2
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +86 -26
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +6 -2
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +2 -39
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +6 -6
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +8 -3
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +13 -1
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +104 -0
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +70 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.js +101 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +23 -5
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +31 -6
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js +5 -4
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +30 -5
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +6 -6
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +6 -11
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +2 -2
- package/out/zero-cache/src/workers/replicator.d.ts +1 -0
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +4 -2
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-client/src/client/context.d.ts +15 -0
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +44 -0
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js +2 -2
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +2 -1
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +50 -13
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.d.ts +5 -5
- package/out/zero-protocol/src/analyze-query-result.d.ts.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.js +1 -1
- package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +30 -30
- package/out/zero-protocol/src/inspect-down.d.ts +6 -6
- package/out/zero-protocol/src/protocol-version.d.ts +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +24 -5
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-solid/src/use-query.js +4 -1
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +16 -10
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +5 -3
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.d.ts +44 -0
- package/out/zql/src/ivm/deferred-input.d.ts.map +1 -0
- package/out/zql/src/ivm/deferred-input.js +88 -0
- package/out/zql/src/ivm/deferred-input.js.map +1 -0
- package/out/zql/src/ivm/filter-operators.d.ts +3 -2
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +19 -4
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +22 -14
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/operator.d.ts +40 -0
- package/out/zql/src/ivm/operator.d.ts.map +1 -1
- package/out/zql/src/ivm/operator.js.map +1 -1
- package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
- package/out/zql/src/planner/planner-builder.js +14 -4
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts +13 -0
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +42 -7
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-debug.d.ts +1 -1
- package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/planner/planner-fan-out.d.ts +1 -0
- package/out/zql/src/planner/planner-fan-out.d.ts.map +1 -1
- package/out/zql/src/planner/planner-fan-out.js +3 -0
- package/out/zql/src/planner/planner-fan-out.js.map +1 -1
- package/out/zql/src/planner/planner-filter.d.ts +59 -0
- package/out/zql/src/planner/planner-filter.d.ts.map +1 -0
- package/out/zql/src/planner/planner-filter.js +116 -0
- package/out/zql/src/planner/planner-filter.js.map +1 -0
- package/out/zql/src/planner/planner-graph.d.ts +2 -0
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +2 -0
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-join.d.ts +2 -0
- package/out/zql/src/planner/planner-join.d.ts.map +1 -1
- package/out/zql/src/planner/planner-join.js +7 -0
- package/out/zql/src/planner/planner-join.js.map +1 -1
- package/out/zql/src/planner/planner-node.d.ts +2 -1
- package/out/zql/src/planner/planner-node.d.ts.map +1 -1
- package/out/zql/src/planner/planner-node.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +6 -0
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +73 -10
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-delegate.d.ts +22 -0
- package/out/zql/src/query/query-delegate.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +12 -11
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +383 -60
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-transitions.d.ts +171 -0
- package/out/zql/src/query/query-transitions.d.ts.map +1 -0
- package/out/zql/src/query/query-transitions.js +207 -0
- package/out/zql/src/query/query-transitions.js.map +1 -0
- package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js +21 -1
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/out/zqlite/src/query-builder.d.ts +1 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +2 -1
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +2 -30
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +10 -3
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-connection.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-connection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,iBAAiB;;IAC5B,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAU;IAQtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAGtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAM7B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"planner-connection.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -71,6 +71,16 @@ var PlannerConnection = class {
|
|
|
71
71
|
* a single join in the UFO is flipped - other branches report undefined.
|
|
72
72
|
*/
|
|
73
73
|
#constraints;
|
|
74
|
+
/**
|
|
75
|
+
* Per-branch filters registered by `PlannerFilter` nodes (simple OR
|
|
76
|
+
* branches) when the enclosing FanIn is in UFI mode. Key is the same
|
|
77
|
+
* `branchPattern.join(',')` used by `#constraints`.
|
|
78
|
+
*
|
|
79
|
+
* These are AND-merged with `#filters` when calling the cost model so
|
|
80
|
+
* the model sees the true filter that the runtime would push to the
|
|
81
|
+
* source for this specific branch.
|
|
82
|
+
*/
|
|
83
|
+
#perBranchFilters = /* @__PURE__ */ new Map();
|
|
74
84
|
#isRoot;
|
|
75
85
|
/**
|
|
76
86
|
* Cached per-constraint costs to avoid redundant cost model calls.
|
|
@@ -89,11 +99,7 @@ var PlannerConnection = class {
|
|
|
89
99
|
this.limit = limit;
|
|
90
100
|
this.#constraints = /* @__PURE__ */ new Map();
|
|
91
101
|
this.#isRoot = isRoot;
|
|
92
|
-
|
|
93
|
-
const costWithFilters = model(table, sort, filters, void 0);
|
|
94
|
-
const costWithoutFilters = model(table, sort, void 0, void 0);
|
|
95
|
-
this.selectivity = costWithoutFilters.rows > 0 ? costWithFilters.rows / costWithoutFilters.rows : 1;
|
|
96
|
-
} else this.selectivity = 1;
|
|
102
|
+
this.selectivity = this.#computeSelectivity(filters);
|
|
97
103
|
}
|
|
98
104
|
setOutput(node) {
|
|
99
105
|
this.#output = node;
|
|
@@ -132,19 +138,35 @@ var PlannerConnection = class {
|
|
|
132
138
|
from: from?.kind ?? "unknown"
|
|
133
139
|
});
|
|
134
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Register a per-branch filter for the given branch pattern. Called by
|
|
143
|
+
* `PlannerFilter` (a simple OR branch) when the enclosing FanIn is in
|
|
144
|
+
* UFI mode. The filter is AND-merged with `this.#filters` when the cost
|
|
145
|
+
* model is invoked for this branch.
|
|
146
|
+
*/
|
|
147
|
+
setPerBranchFilter(path, filter) {
|
|
148
|
+
this.#perBranchFilters.set(path.join(","), filter);
|
|
149
|
+
this.#cachedConstraintCosts.clear();
|
|
150
|
+
}
|
|
135
151
|
estimateCost(downstreamChildSelectivity, branchPattern, planDebugger) {
|
|
136
152
|
const key = branchPattern.join(",");
|
|
137
153
|
let cost = this.#cachedConstraintCosts.get(key);
|
|
138
154
|
if (cost !== void 0) return cost;
|
|
139
155
|
const constraint = this.#constraints.get(key);
|
|
140
156
|
const mergedConstraint = mergeConstraints(this.#baseConstraints, constraint);
|
|
141
|
-
const
|
|
157
|
+
const perBranchFilter = this.#perBranchFilters.get(key);
|
|
158
|
+
const effectiveFilters = this.#filters && perBranchFilter ? {
|
|
159
|
+
type: "and",
|
|
160
|
+
conditions: [this.#filters, perBranchFilter]
|
|
161
|
+
} : this.#filters ?? perBranchFilter;
|
|
162
|
+
const { startupCost, fanout, rows } = this.#model(this.table, this.#sort, effectiveFilters, mergedConstraint);
|
|
163
|
+
const selectivity = perBranchFilter ? this.#computeSelectivity(effectiveFilters) : this.selectivity;
|
|
142
164
|
cost = {
|
|
143
165
|
startupCost,
|
|
144
166
|
scanEst: this.limit === void 0 ? rows : Math.min(rows, this.limit / downstreamChildSelectivity),
|
|
145
167
|
cost: 0,
|
|
146
168
|
returnedRows: rows,
|
|
147
|
-
selectivity
|
|
169
|
+
selectivity,
|
|
148
170
|
limit: this.limit,
|
|
149
171
|
fanout
|
|
150
172
|
};
|
|
@@ -161,6 +183,12 @@ var PlannerConnection = class {
|
|
|
161
183
|
});
|
|
162
184
|
return cost;
|
|
163
185
|
}
|
|
186
|
+
#computeSelectivity(filters) {
|
|
187
|
+
if (this.#baseLimit === void 0 || filters === void 0) return 1;
|
|
188
|
+
const costWithFilters = this.#model(this.table, this.#sort, filters, void 0);
|
|
189
|
+
const costWithoutFilters = this.#model(this.table, this.#sort, void 0, void 0);
|
|
190
|
+
return costWithoutFilters.rows > 0 ? costWithFilters.rows / costWithoutFilters.rows : 1;
|
|
191
|
+
}
|
|
164
192
|
/**
|
|
165
193
|
* Remove the limit from this connection.
|
|
166
194
|
* Called when a parent join is flipped, making this connection part of an
|
|
@@ -179,6 +207,7 @@ var PlannerConnection = class {
|
|
|
179
207
|
}
|
|
180
208
|
reset() {
|
|
181
209
|
this.#constraints.clear();
|
|
210
|
+
this.#perBranchFilters.clear();
|
|
182
211
|
this.limit = this.#baseLimit;
|
|
183
212
|
this.#cachedConstraintCosts.clear();
|
|
184
213
|
}
|
|
@@ -192,9 +221,15 @@ var PlannerConnection = class {
|
|
|
192
221
|
/**
|
|
193
222
|
* Restore constraint state from a snapshot.
|
|
194
223
|
* Used by PlannerGraph to restore planning state.
|
|
224
|
+
*
|
|
225
|
+
* Per-branch filters are not snapshotted — they are re-derived by the
|
|
226
|
+
* subsequent `propagateConstraints` pass. Clearing them here ensures
|
|
227
|
+
* that any entries left over from the previous iteration don't linger
|
|
228
|
+
* in case the restored plan registers a different set.
|
|
195
229
|
*/
|
|
196
230
|
restoreConstraints(constraints) {
|
|
197
231
|
this.#constraints.clear();
|
|
232
|
+
this.#perBranchFilters.clear();
|
|
198
233
|
for (const [key, value] of constraints) this.#constraints.set(key, value);
|
|
199
234
|
this.#cachedConstraintCosts.clear();
|
|
200
235
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-connection.js","names":["#sort","#filters","#model","#baseConstraints","#baseLimit","#constraints","#isRoot","#output","#cachedConstraintCosts"],"sources":["../../../../../zql/src/planner/planner-connection.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {Condition, Ordering} from '../../../zero-protocol/src/ast.ts';\nimport {\n mergeConstraints,\n type PlannerConstraint,\n} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\n\n/**\n * Represents a connection to a source (table scan).\n *\n * # Dual State Pattern\n * Like all planner nodes, PlannerConnection separates:\n * 1. immutable structure: Ordering, filters, cost model (set at construction)\n * 2. mutable state: Pinned status, constraints (mutated during planning)\n *\n * # Cost Estimation\n * The ordering and filters determine the initial cost. As planning progresses,\n * constraints from parent joins refine the cost estimate.\n *\n * # Constraint Flow\n * When a connection is pinned as the outer loop, it reveals constraints for\n * connected joins. These constraints propagate through the graph, allowing\n * other connections to update their cost estimates.\n *\n * Example:\n *\n * ```ts\n * builder.issue.whereExists('assignee', a => a.where('name', 'Alice'))\n * ```\n *\n * ```\n * [issue] [assignee]\n * | |\n * | +-- where name = 'Alice'\n * \\ /\n * \\ /\n * [join]\n * |\n * ```\n *\n * - Initial state: Both connections have no constraints, costs are unconstrained\n * - If `issue` chosen first: Reveals constraint `assignee_id` for assignee connection\n * - If `assignee` chosen first: Reveals constraint `assignee_id` for issue connection\n * - Updated costs guide the next selection\n *\n * # Lifecycle\n * 1. Construct with immutable structure (ordering, filters, cost model)\n * 2. Wire to output node during graph construction\n * 3. Planning mutates pinned status and accumulates constraints\n * 4. reset() clears mutable state for replanning\n */\nexport class PlannerConnection {\n readonly kind = 'connection' as const;\n\n // ========================================================================\n // IMMUTABLE STRUCTURE (set during construction, never changes)\n // ========================================================================\n readonly #sort: Ordering;\n readonly #filters: Condition | undefined;\n readonly #model: ConnectionCostModel;\n readonly table: string;\n readonly name: string; // Human-readable name for debugging (defaults to table name)\n readonly #baseConstraints: PlannerConstraint | undefined; // Constraints from parent correlation\n readonly #baseLimit: number | undefined; // Original limit from query structure (never modified)\n readonly selectivity: number; // Fraction of rows passing filters (1.0 = no filtering)\n #output?: PlannerNode | undefined; // Set once during graph construction\n\n // ========================================================================\n // MUTABLE PLANNING STATE (changes during plan search)\n // ========================================================================\n /**\n * Current limit during planning. Can be cleared (set to undefined) when a\n * parent join is flipped, indicating this connection is now in an outer loop\n * and should not be limited by EXISTS semantics.\n */\n limit: number | undefined;\n\n /**\n * Constraints accumulated from parent joins during planning.\n * Key is a path through the graph (e.g., \"0,1\" for branch pattern [0,1]).\n *\n * Undefined constraints are possible when a FO converts to UFO and only\n * a single join in the UFO is flipped - other branches report undefined.\n */\n readonly #constraints: Map<string, PlannerConstraint | undefined>;\n\n readonly #isRoot: boolean;\n\n /**\n * Cached per-constraint costs to avoid redundant cost model calls.\n * Maps constraint key (branch pattern string) to computed cost.\n * Invalidated when constraints change.\n */\n #cachedConstraintCosts: Map<string, CostEstimate> = new Map();\n\n constructor(\n table: string,\n model: ConnectionCostModel,\n sort: Ordering,\n filters: Condition | undefined,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n limit?: number,\n name?: string,\n ) {\n this.table = table;\n this.name = name ?? table;\n this.#sort = sort;\n this.#filters = filters;\n this.#model = model;\n this.#baseConstraints = baseConstraints;\n this.#baseLimit = limit;\n this.limit = limit;\n this.#constraints = new Map();\n this.#isRoot = isRoot;\n\n // Compute selectivity for EXISTS child connections (baseLimit === 1)\n // Selectivity = fraction of rows that pass filters\n if (limit !== undefined && filters) {\n const costWithFilters = model(table, sort, filters, undefined);\n const costWithoutFilters = model(table, sort, undefined, undefined);\n this.selectivity =\n costWithoutFilters.rows > 0\n ? costWithFilters.rows / costWithoutFilters.rows\n : 1.0;\n } else {\n // Root connections or connections without filters\n this.selectivity = 1.0;\n }\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return 'connection';\n }\n\n /**\n * Constraints are uniquely identified by their path through the\n * graph.\n *\n * FO represents all sub-joins as a single path.\n * UFO represents each sub-join as a separate path.\n * The first branch in a UFO will match the path of FO so no re-set needs to happen\n * when swapping from FO to UFO.\n *\n * FO swaps to UFO when a join inside FO-FI gets flipped.\n *\n * The max of the last element of the paths is the number of\n * root branches.\n */\n propagateConstraints(\n path: number[],\n c: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n const key = path.join(',');\n this.#constraints.set(key, c);\n // Constraints changed, invalidate cost caches\n this.#cachedConstraintCosts.clear();\n\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'connection',\n node: this.name,\n branchPattern: path,\n constraint: c,\n from: from?.kind ?? 'unknown',\n });\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n // Branch pattern specified - return cost for this specific branch\n const key = branchPattern.join(',');\n\n // Check per-constraint cache first\n let cost = this.#cachedConstraintCosts.get(key);\n if (cost !== undefined) {\n return cost;\n }\n\n // Cache miss - compute and cache\n const constraint = this.#constraints.get(key);\n // Merge base constraints with propagated constraints\n const mergedConstraint = mergeConstraints(\n this.#baseConstraints,\n constraint,\n );\n const {startupCost, fanout, rows} = this.#model(\n this.table,\n this.#sort,\n this.#filters,\n mergedConstraint,\n );\n cost = {\n startupCost,\n scanEst:\n this.limit === undefined\n ? rows\n : Math.min(rows, this.limit / downstreamChildSelectivity),\n cost: 0,\n returnedRows: rows,\n selectivity: this.selectivity,\n limit: this.limit,\n fanout,\n };\n this.#cachedConstraintCosts.set(key, cost);\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'connection',\n node: this.name,\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(cost),\n filters: this.#filters,\n ordering: this.#sort,\n });\n }\n\n return cost;\n }\n\n /**\n * Remove the limit from this connection.\n * Called when a parent join is flipped, making this connection part of an\n * outer loop that should produce all rows rather than stopping at the limit.\n */\n unlimit(): void {\n if (this.#isRoot) {\n // We cannot unlimit root connections\n return;\n }\n if (this.limit !== undefined) {\n this.limit = undefined;\n // Limit changes do not impact connection costs.\n // Limit is taken into account at the join level.\n // Given that, we do not need to invalidate cost caches here.\n }\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * For connections, we simply remove the limit.\n */\n propagateUnlimitFromFlippedJoin(): void {\n this.unlimit();\n }\n\n reset() {\n this.#constraints.clear();\n this.limit = this.#baseLimit;\n // Clear all cost caches\n this.#cachedConstraintCosts.clear();\n }\n\n /**\n * Capture constraint state for snapshotting.\n * Used by PlannerGraph to save/restore planning state.\n */\n captureConstraints(): Map<string, PlannerConstraint | undefined> {\n return new Map(this.#constraints);\n }\n\n /**\n * Restore constraint state from a snapshot.\n * Used by PlannerGraph to restore planning state.\n */\n restoreConstraints(\n constraints: Map<string, PlannerConstraint | undefined>,\n ): void {\n this.#constraints.clear();\n for (const [key, value] of constraints) {\n this.#constraints.set(key, value);\n }\n // Constraints changed, invalidate cost caches\n this.#cachedConstraintCosts.clear();\n }\n\n /** Get current constraints for debugging. */\n getConstraintsForDebug(): Record<string, PlannerConstraint | undefined> {\n const record: Record<string, PlannerConstraint | undefined> = {};\n for (const [key, value] of this.#constraints) {\n record[key] = value;\n }\n return record;\n }\n\n /** Get filters for debugging. */\n getFiltersForDebug(): Condition | undefined {\n return this.#filters;\n }\n\n /** Get sort/ordering for debugging. */\n getSortForDebug(): Ordering {\n return this.#sort;\n }\n\n /** Get estimated cost for each constraint branch. */\n getConstraintCostsForDebug(): Record<string, CostEstimate> {\n const record: Record<string, CostEstimate> = {};\n for (const [key, value] of this.#cachedConstraintCosts) {\n record[key] = value;\n }\n return record;\n }\n}\n\ntype FanoutEst = {\n fanout: number;\n confidence: 'high' | 'med' | 'none';\n};\nexport type FanoutCostModel = (columns: string[]) => FanoutEst;\n\nexport type CostModelCost = {\n startupCost: number;\n rows: number;\n fanout: FanoutCostModel;\n};\nexport type ConnectionCostModel = (\n table: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n) => CostModelCost;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner-connection.js","names":["#sort","#filters","#model","#baseConstraints","#baseLimit","#constraints","#perBranchFilters","#isRoot","#computeSelectivity","#output","#cachedConstraintCosts"],"sources":["../../../../../zql/src/planner/planner-connection.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {Condition, Ordering} from '../../../zero-protocol/src/ast.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport {\n mergeConstraints,\n type PlannerConstraint,\n} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\n\n/**\n * Represents a connection to a source (table scan).\n *\n * # Dual State Pattern\n * Like all planner nodes, PlannerConnection separates:\n * 1. immutable structure: Ordering, filters, cost model (set at construction)\n * 2. mutable state: Pinned status, constraints (mutated during planning)\n *\n * # Cost Estimation\n * The ordering and filters determine the initial cost. As planning progresses,\n * constraints from parent joins refine the cost estimate.\n *\n * # Constraint Flow\n * When a connection is pinned as the outer loop, it reveals constraints for\n * connected joins. These constraints propagate through the graph, allowing\n * other connections to update their cost estimates.\n *\n * Example:\n *\n * ```ts\n * builder.issue.whereExists('assignee', a => a.where('name', 'Alice'))\n * ```\n *\n * ```\n * [issue] [assignee]\n * | |\n * | +-- where name = 'Alice'\n * \\ /\n * \\ /\n * [join]\n * |\n * ```\n *\n * - Initial state: Both connections have no constraints, costs are unconstrained\n * - If `issue` chosen first: Reveals constraint `assignee_id` for assignee connection\n * - If `assignee` chosen first: Reveals constraint `assignee_id` for issue connection\n * - Updated costs guide the next selection\n *\n * # Lifecycle\n * 1. Construct with immutable structure (ordering, filters, cost model)\n * 2. Wire to output node during graph construction\n * 3. Planning mutates pinned status and accumulates constraints\n * 4. reset() clears mutable state for replanning\n */\nexport class PlannerConnection {\n readonly kind = 'connection' as const;\n\n // ========================================================================\n // IMMUTABLE STRUCTURE (set during construction, never changes)\n // ========================================================================\n readonly #sort: Ordering;\n readonly #filters: Condition | undefined;\n readonly #model: ConnectionCostModel;\n readonly table: string;\n readonly name: string; // Human-readable name for debugging (defaults to table name)\n readonly #baseConstraints: PlannerConstraint | undefined; // Constraints from parent correlation\n readonly #baseLimit: number | undefined; // Original limit from query structure (never modified)\n readonly selectivity: number; // Fraction of rows passing filters (1.0 = no filtering)\n #output?: PlannerNode | undefined; // Set once during graph construction\n\n // ========================================================================\n // MUTABLE PLANNING STATE (changes during plan search)\n // ========================================================================\n /**\n * Current limit during planning. Can be cleared (set to undefined) when a\n * parent join is flipped, indicating this connection is now in an outer loop\n * and should not be limited by EXISTS semantics.\n */\n limit: number | undefined;\n\n /**\n * Constraints accumulated from parent joins during planning.\n * Key is a path through the graph (e.g., \"0,1\" for branch pattern [0,1]).\n *\n * Undefined constraints are possible when a FO converts to UFO and only\n * a single join in the UFO is flipped - other branches report undefined.\n */\n readonly #constraints: Map<string, PlannerConstraint | undefined>;\n\n /**\n * Per-branch filters registered by `PlannerFilter` nodes (simple OR\n * branches) when the enclosing FanIn is in UFI mode. Key is the same\n * `branchPattern.join(',')` used by `#constraints`.\n *\n * These are AND-merged with `#filters` when calling the cost model so\n * the model sees the true filter that the runtime would push to the\n * source for this specific branch.\n */\n readonly #perBranchFilters: Map<string, NoSubqueryCondition> = new Map();\n\n readonly #isRoot: boolean;\n\n /**\n * Cached per-constraint costs to avoid redundant cost model calls.\n * Maps constraint key (branch pattern string) to computed cost.\n * Invalidated when constraints change.\n */\n #cachedConstraintCosts: Map<string, CostEstimate> = new Map();\n\n constructor(\n table: string,\n model: ConnectionCostModel,\n sort: Ordering,\n filters: Condition | undefined,\n isRoot: boolean,\n baseConstraints?: PlannerConstraint,\n limit?: number,\n name?: string,\n ) {\n this.table = table;\n this.name = name ?? table;\n this.#sort = sort;\n this.#filters = filters;\n this.#model = model;\n this.#baseConstraints = baseConstraints;\n this.#baseLimit = limit;\n this.limit = limit;\n this.#constraints = new Map();\n this.#isRoot = isRoot;\n\n this.selectivity = this.#computeSelectivity(filters);\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return 'connection';\n }\n\n /**\n * Constraints are uniquely identified by their path through the\n * graph.\n *\n * FO represents all sub-joins as a single path.\n * UFO represents each sub-join as a separate path.\n * The first branch in a UFO will match the path of FO so no re-set needs to happen\n * when swapping from FO to UFO.\n *\n * FO swaps to UFO when a join inside FO-FI gets flipped.\n *\n * The max of the last element of the paths is the number of\n * root branches.\n */\n propagateConstraints(\n path: number[],\n c: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n const key = path.join(',');\n this.#constraints.set(key, c);\n // Constraints changed, invalidate cost caches\n this.#cachedConstraintCosts.clear();\n\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'connection',\n node: this.name,\n branchPattern: path,\n constraint: c,\n from: from?.kind ?? 'unknown',\n });\n }\n\n /**\n * Register a per-branch filter for the given branch pattern. Called by\n * `PlannerFilter` (a simple OR branch) when the enclosing FanIn is in\n * UFI mode. The filter is AND-merged with `this.#filters` when the cost\n * model is invoked for this branch.\n */\n setPerBranchFilter(path: number[], filter: NoSubqueryCondition): void {\n this.#perBranchFilters.set(path.join(','), filter);\n // The cost depends on the filter, so invalidate caches.\n this.#cachedConstraintCosts.clear();\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n // Branch pattern specified - return cost for this specific branch\n const key = branchPattern.join(',');\n\n // Check per-constraint cache first\n let cost = this.#cachedConstraintCosts.get(key);\n if (cost !== undefined) {\n return cost;\n }\n\n // Cache miss - compute and cache\n const constraint = this.#constraints.get(key);\n // Merge base constraints with propagated constraints\n const mergedConstraint = mergeConstraints(\n this.#baseConstraints,\n constraint,\n );\n // AND the per-branch filter (registered by a PlannerFilter for a\n // simple OR branch in UFI mode) with the connection-time filter so\n // the cost model sees the same effective filter the runtime applies.\n const perBranchFilter = this.#perBranchFilters.get(key);\n const effectiveFilters: Condition | undefined =\n this.#filters && perBranchFilter\n ? {\n type: 'and',\n conditions: [this.#filters, perBranchFilter],\n }\n : (this.#filters ?? perBranchFilter);\n const {startupCost, fanout, rows} = this.#model(\n this.table,\n this.#sort,\n effectiveFilters,\n mergedConstraint,\n );\n const selectivity = perBranchFilter\n ? this.#computeSelectivity(effectiveFilters)\n : this.selectivity;\n cost = {\n startupCost,\n scanEst:\n this.limit === undefined\n ? rows\n : Math.min(rows, this.limit / downstreamChildSelectivity),\n cost: 0,\n returnedRows: rows,\n selectivity,\n limit: this.limit,\n fanout,\n };\n this.#cachedConstraintCosts.set(key, cost);\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'connection',\n node: this.name,\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(cost),\n filters: this.#filters,\n ordering: this.#sort,\n });\n }\n\n return cost;\n }\n\n #computeSelectivity(filters: Condition | undefined): number {\n if (this.#baseLimit === undefined || filters === undefined) {\n return 1.0;\n }\n\n const costWithFilters = this.#model(\n this.table,\n this.#sort,\n filters,\n undefined,\n );\n const costWithoutFilters = this.#model(\n this.table,\n this.#sort,\n undefined,\n undefined,\n );\n return costWithoutFilters.rows > 0\n ? costWithFilters.rows / costWithoutFilters.rows\n : 1.0;\n }\n\n /**\n * Remove the limit from this connection.\n * Called when a parent join is flipped, making this connection part of an\n * outer loop that should produce all rows rather than stopping at the limit.\n */\n unlimit(): void {\n if (this.#isRoot) {\n // We cannot unlimit root connections\n return;\n }\n if (this.limit !== undefined) {\n this.limit = undefined;\n // Limit changes do not impact connection costs.\n // Limit is taken into account at the join level.\n // Given that, we do not need to invalidate cost caches here.\n }\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * For connections, we simply remove the limit.\n */\n propagateUnlimitFromFlippedJoin(): void {\n this.unlimit();\n }\n\n reset() {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n this.limit = this.#baseLimit;\n // Clear all cost caches\n this.#cachedConstraintCosts.clear();\n }\n\n /**\n * Capture constraint state for snapshotting.\n * Used by PlannerGraph to save/restore planning state.\n */\n captureConstraints(): Map<string, PlannerConstraint | undefined> {\n return new Map(this.#constraints);\n }\n\n /**\n * Restore constraint state from a snapshot.\n * Used by PlannerGraph to restore planning state.\n *\n * Per-branch filters are not snapshotted — they are re-derived by the\n * subsequent `propagateConstraints` pass. Clearing them here ensures\n * that any entries left over from the previous iteration don't linger\n * in case the restored plan registers a different set.\n */\n restoreConstraints(\n constraints: Map<string, PlannerConstraint | undefined>,\n ): void {\n this.#constraints.clear();\n this.#perBranchFilters.clear();\n for (const [key, value] of constraints) {\n this.#constraints.set(key, value);\n }\n // Constraints changed, invalidate cost caches\n this.#cachedConstraintCosts.clear();\n }\n\n /** Get current constraints for debugging. */\n getConstraintsForDebug(): Record<string, PlannerConstraint | undefined> {\n const record: Record<string, PlannerConstraint | undefined> = {};\n for (const [key, value] of this.#constraints) {\n record[key] = value;\n }\n return record;\n }\n\n /** Get filters for debugging. */\n getFiltersForDebug(): Condition | undefined {\n return this.#filters;\n }\n\n /** Get sort/ordering for debugging. */\n getSortForDebug(): Ordering {\n return this.#sort;\n }\n\n /** Get estimated cost for each constraint branch. */\n getConstraintCostsForDebug(): Record<string, CostEstimate> {\n const record: Record<string, CostEstimate> = {};\n for (const [key, value] of this.#cachedConstraintCosts) {\n record[key] = value;\n }\n return record;\n }\n}\n\ntype FanoutEst = {\n fanout: number;\n confidence: 'high' | 'med' | 'none';\n};\nexport type FanoutCostModel = (columns: string[]) => FanoutEst;\n\nexport type CostModelCost = {\n startupCost: number;\n rows: number;\n fanout: FanoutCostModel;\n};\nexport type ConnectionCostModel = (\n table: string,\n sort: Ordering,\n filters: Condition | undefined,\n constraint: PlannerConstraint | undefined,\n) => CostModelCost;\n"],"mappings":"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"}
|
|
@@ -74,7 +74,7 @@ export type BestPlanSelectedEvent = v.Infer<typeof bestPlanSelectedEventJSONSche
|
|
|
74
74
|
export type NodeCostEvent = {
|
|
75
75
|
type: 'node-cost';
|
|
76
76
|
attemptNumber?: number;
|
|
77
|
-
nodeType: 'connection' | 'join' | 'fan-out' | 'fan-in' | 'terminus';
|
|
77
|
+
nodeType: 'connection' | 'join' | 'fan-out' | 'fan-in' | 'filter' | 'terminus';
|
|
78
78
|
node: string;
|
|
79
79
|
branchPattern: number[];
|
|
80
80
|
downstreamChildSelectivity: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-debug.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-debug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACxD,OAAO,KAAK,EACV,2BAA2B,EAC3B,+BAA+B,EAC/B,iCAAiC,EACjC,6BAA6B,EAC7B,kBAAkB,EAClB,yBAAyB,EAC1B,MAAM,oDAAoD,CAAC;AAC5D,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAET,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,mBAAmB,CAAC;AAE9D;;;;GAIG;AAEH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAE5E;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,kBAAkB,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,EAAE,OAAO,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC;QAC3D,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;KAC/D,CAAC,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,iCAAiC,CACzC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,KAAK,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC;QAC3D,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;KAC/D,CAAC,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,eAAe,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,QAAQ,CAAC;KAChB,CAAC,CAAC;IAEH,YAAY,EAAE,SAAS,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CACzC,OAAO,+BAA+B,CACvC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,
|
|
1
|
+
{"version":3,"file":"planner-debug.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-debug.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType: 'connection' | 'join' | 'fan-out' | 'fan-in' | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n let attemptEvents = eventsByAttempt.get(attempt);\n if (!attemptEvents) {\n attemptEvents = [];\n eventsByAttempt.set(attempt, attemptEvents);\n }\n attemptEvents.push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType:\n | 'connection'\n | 'join'\n | 'fan-out'\n | 'fan-in'\n | 'filter'\n | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n let attemptEvents = eventsByAttempt.get(attempt);\n if (!attemptEvents) {\n attemptEvents = [];\n eventsByAttempt.set(attempt, attemptEvents);\n }\n attemptEvents.push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":";;;;;AAqJA,IAAa,sBAAb,MAAyD;CACvD,SAAoC,CAAC;CACrC,iBAAyB;CAEzB,IAAI,OAA6B;EAE/B,IAAI,MAAM,SAAS,iBACjB,KAAK,iBAAiB,MAAM;EAI9B,IAAI,MAAM,SAAS,eAAe,MAAM,SAAS,mBAC/C,MAA+C,gBAC7C,KAAK;EAGT,KAAK,OAAO,KAAK,KAAK;CACxB;;;;CAKA,UACE,MACsC;EACtC,OAAO,KAAK,OAAO,QAAO,MAAK,EAAE,SAAS,IAAI;CAIhD;;;;CAKA,SAAiB;EACf,OAAO,oBAAoB,KAAK,MAAM;CACxC;AACF;;;;AAKA,SAAS,iBACP,YACQ;CACR,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,OAAO,OAAO,KAAK,UAAU;CACnC,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,OAAO,MAAM,KAAK,KAAK,IAAI,IAAI;AACjC;;;;AAKA,SAAS,oBAAoB,OAA8B;CACzD,QAAQ,MAAM,MAAd;EACE,KAAK,UACH,OAAO,MAAM;EACf,KAAK;GAEH,IAAI,OAAO,MAAM,UAAU,UACzB,OAAO,IAAI,MAAM,MAAM;GAEzB,OAAO,KAAK,UAAU,MAAM,KAAK;EACnC,KAAK,UACH,OAAO,IAAI,MAAM,OAAO,GAAG,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,MAAM,KAAK,GAAG,IAAI,MAAM;CAC1F;AACF;;;;AAKA,SAAS,aAAa,QAAuC;CAC3D,IAAI,CAAC,QAAQ,OAAO;CAEpB,QAAQ,OAAO,MAAf;EACE,KAAK,UACH,OAAO,GAAG,oBAAoB,OAAO,IAAI,EAAE,GAAG,OAAO,GAAG,GAAG,oBAAoB,OAAO,KAAK;EAC7F,KAAK,OACH,OAAO,IAAI,OAAO,WAAW,IAAI,YAAY,EAAE,KAAK,OAAO,EAAE;EAC/D,KAAK,MACH,OAAO,IAAI,OAAO,WAAW,IAAI,YAAY,EAAE,KAAK,MAAM,EAAE;EAC9D,KAAK,sBACH,OAAO,UAAU,OAAO,QAAQ,SAAS,MAAM;EACjD,SACE,OAAO,KAAK,UAAU,MAAM;CAChC;AACF;;;;AAKA,SAAS,eAAe,UAAwC;CAC9D,IAAI,CAAC,YAAY,SAAS,WAAW,GAAG,OAAO;CAC/C,OAAO,SACJ,KAAK,CAAC,OAAO,eAAe,GAAG,MAAM,GAAG,WAAW,EACnD,KAAK,IAAI;AACd;;;;AAKA,SAAS,qBACP,YACA,QACU;CACV,MAAM,QAAkB,CAAC;CAMzB,MAAM,gBAHa,OAAO,MAAK,MAAK,EAAE,SAAS,eAGzB,GAAY,iBAAiB;CAGnD,MAAM,UACJ,OAAO,kBAAkB,WACrB,KAAK,KAAK,KAAK,KAAK,aAAa,CAAC,KAAK,IACvC;CACN,MAAM,aAAa,WAAW,SAAS,CAAC,EAAE,SAAS,SAAS,GAAG;CAE/D,MAAM,KACJ,YAAY,aAAa,EAAE,GAAG,cAAc,YAAY,WAAW,IAAI,WAAW,EACpF;CAGA,MAAM,uBAGA,CAAC;CACP,MAAM,6BAAoD,CAAC;CAE3D,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,MAAM,SAAS,eAAe,MAAM,aAAa,cACnD,qBAAqB,KAAK,KAAK;EAEjC,IAAI,MAAM,SAAS,qBAAqB,MAAM,aAAa,cACzD,2BAA2B,KAAK,KAAK;CAEzC;CAGA,IAAI,qBAAqB,SAAS,GAAG;EACnC,MAAM,KAAK,gBAAgB;EAC3B,KAAK,MAAM,QAAQ,sBAAsB;GAEvC,MAAM,aAAa,2BAA2B,MAC5C,MACE,EAAE,SAAS,KAAK,QAChB,EAAE,cAAc,KAAK,GAAG,MAAM,KAAK,cAAc,KAAK,GAAG,CAC7D,GAAG;GAEH,MAAM,gBAAgB,iBAAiB,UAAU;GACjD,MAAM,YAAY,aAAa,KAAK,OAAO;GAC3C,MAAM,cAAc,eAAe,KAAK,QAAQ;GAChD,MAAM,WACJ,KAAK,aAAa,UAAU,KAAA,IACxB,KAAK,aAAa,MAAM,SAAS,IACjC;GAEN,MAAM,KAAK,OAAO,KAAK,KAAK,EAAE;GAC9B,MAAM,KACJ,cAAc,KAAK,aAAa,KAAK,QAAQ,CAAC,EAAE,YAAY,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,SAAS,KAAK,aAAa,QAAQ,QAAQ,CAAC,GACnJ;GACA,MAAM,KACJ,cAAc,KAAK,aAAa,aAAa,QAAQ,CAAC,EAAE,gBAAgB,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,UAAU,UAC7H;GACA,MAAM,KACJ,oCAAoC,KAAK,2BAA2B,QAAQ,CAAC,GAC/E;GACA,MAAM,KAAK,qBAAqB,eAAe;GAC/C,MAAM,KAAK,iBAAiB,WAAW;GACvC,MAAM,KAAK,kBAAkB,aAAa;EAC5C;CACF;CAGA,MAAM,YAGA,CAAC;CACP,KAAK,MAAM,SAAS,QAClB,IAAI,MAAM,SAAS,eAAe,MAAM,aAAa,QACnD,UAAU,KAAK,KAAK;CAIxB,IAAI,UAAU,SAAS,GAAG;EACxB,MAAM,KAAK,UAAU;EACrB,KAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,UAAU,KAAK,WAAW,KAAK,KAAK,SAAS,KAAK;GACxD,MAAM,WACJ,KAAK,aAAa,UAAU,KAAA,IACxB,KAAK,aAAa,MAAM,SAAS,IACjC;GAEN,MAAM,KAAK,OAAO,KAAK,OAAO,QAAQ,EAAE;GACxC,MAAM,KACJ,cAAc,KAAK,aAAa,KAAK,QAAQ,CAAC,EAAE,YAAY,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,SAAS,KAAK,aAAa,QAAQ,QAAQ,CAAC,GACnJ;GACA,MAAM,KACJ,cAAc,KAAK,aAAa,aAAa,QAAQ,CAAC,EAAE,gBAAgB,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,UAAU,UAC7H;GACA,MAAM,KACJ,oCAAoC,KAAK,2BAA2B,QAAQ,CAAC,GAC/E;EACF;CACF;CAGA,MAAM,gBAAgB,OAAO,MAAK,MAAK,EAAE,SAAS,eAAe;CAGjE,MAAM,cAAc,OAAO,MAAK,MAAK,EAAE,SAAS,aAAa;CAM7D,IAAI,eACF,MAAM,KACJ,mCAAmC,cAAc,UAAU,QAAQ,CAAC,GACtE;MACK,IAAI,aACT,MAAM,KAAK,oBAAoB,YAAY,QAAQ;CAGrD,OAAO;AACT;;;;;AAMA,SAAS,iCACP,YAC4C;CAC5C,IAAI,eAAe,KAAA,GACjB;CAEF,IAAI,eAAe,MACjB,OAAO;CAET,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,UAAU,GAChD,OAAO,OAAO,QAAQ,KAAA,IAAY,OAAO;CAE3C,OAAO;AACT;;;;;;;AAQA,SAAS,eAAe,OAA2C;CAEjE,IAAI,MAAM,SAAS,iBAAiB;EAClC,MAAM,EAAC,cAAc,GAAG,GAAG,SAAQ;EACnC,OAAO;CACT;CAGA,IAAI,MAAM,SAAS,mBACjB,OAAO;EACL,GAAG;EACH,YAAY,iCAAiC,MAAM,UAAU;CAC/D;CAGF,IAAI,MAAM,SAAS,oBACjB,OAAO;EACL,GAAG;EACH,OAAO,MAAM,MAAM,KAAI,UAAS;GAC9B,GAAG;GACH,aAAa,OAAO,YAClB,OAAO,QAAQ,KAAK,WAAW,EAAE,KAAK,CAAC,KAAK,SAAS,CACnD,KACA,iCAAiC,GAAG,CACtC,CAAC,CACH;EACF,EAAE;CACJ;CAGF,IAAI,MAAM,SAAS,0BACjB,OAAO;EACL,GAAG;EACH,uBAAuB,MAAM,sBAAsB,KAAI,QAAO;GAC5D,GAAG;GACH,aAAa,OAAO,YAClB,OAAO,QAAQ,GAAG,WAAW,EAAE,KAAK,CAAC,KAAK,SAAS,CACjD,KACA,iCAAiC,GAAG,CACtC,CAAC,CACH;EACF,EAAE;CACJ;CAGF,OAAO;AACT;;;;;;AAOA,SAAgB,yBACd,QACsB;CACtB,OAAO,OAAO,IAAI,cAAc;AAClC;;;;;;;;;;;;;;;;AAiBA,SAAgB,oBACd,QACQ;CACR,MAAM,QAAkB,CAAC;CAGzB,MAAM,kCAAkB,IAAI,IAG1B;CACF,IAAI;CAUJ,KAAK,MAAM,SAAS,QAClB,IAAI,mBAAmB,OAAO;EAC5B,MAAM,UAAU,MAAM;EACtB,IAAI,YAAY,KAAA,GAAW;GACzB,IAAI,gBAAgB,gBAAgB,IAAI,OAAO;GAC/C,IAAI,CAAC,eAAe;IAClB,gBAAgB,CAAC;IACjB,gBAAgB,IAAI,SAAS,aAAa;GAC5C;GACA,cAAc,KAAK,KAAK;EAC1B;CACF,OAAO,IAAI,MAAM,SAAS,sBAExB,gBAAgB;CAKpB,KAAK,MAAM,CAAC,YAAY,WAAW,gBAAgB,QAAQ,GAAG;EAC5D,MAAM,KAAK,GAAG,qBAAqB,YAAY,MAAM,CAAC;EACtD,MAAM,KAAK,EAAE;CACf;CAGA,IAAI,eAAe;EACjB,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;EACzB,MAAM,KACJ,wBAAwB,cAAc,oBAAoB,EAAE,SAAS,cAAc,UAAU,QAAQ,CAAC,EAAE,EAC1G;EACA,IAAI,cAAc,WAAW,SAAS,GAAG;GACvC,MAAM,KAAK,eAAe;GAC1B,KAAK,MAAM,KAAK,cAAc,YAC5B,MAAM,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM;EAEzC;EACA,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;CAC3B;CAEA,OAAO,MAAM,KAAK,IAAI;AACxB"}
|
|
@@ -9,6 +9,7 @@ export declare class PlannerFanOut {
|
|
|
9
9
|
get type(): "FO" | "UFO";
|
|
10
10
|
addOutput(node: PlannerNode): void;
|
|
11
11
|
get outputs(): PlannerNode[];
|
|
12
|
+
get input(): Exclude<PlannerNode, PlannerTerminus>;
|
|
12
13
|
closestJoinOrSource(): JoinOrConnection;
|
|
13
14
|
propagateConstraints(branchPattern: number[], constraint: PlannerConstraint | undefined, from?: PlannerNode, planDebugger?: PlanDebugger): void;
|
|
14
15
|
estimateCost(downstreamChildSelectivity: number, branchPattern: number[], planDebugger?: PlanDebugger): CostEstimate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-fan-out.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-fan-out.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D,qBAAa,aAAa;;IACxB,QAAQ,CAAC,IAAI,EAAG,SAAS,CAAU;IAKnC,YAAY,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,eAAe,CAAC,EAGvD;IAED,IAAI,IAAI,iBAEP;IAED,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAEjC;IAED,IAAI,OAAO,IAAI,WAAW,EAAE,CAE3B;IAED,mBAAmB,IAAI,gBAAgB,CAEtC;IAED,oBAAoB,CAClB,aAAa,EAAE,MAAM,EAAE,EACvB,UAAU,EAAE,iBAAiB,GAAG,SAAS,EACzC,IAAI,CAAC,EAAE,WAAW,EAClB,YAAY,CAAC,EAAE,YAAY,GAC1B,IAAI,CAgBN;IAED,YAAY,CACV,0BAA0B,EAAE,MAAM,EAClC,aAAa,EAAE,MAAM,EAAE,EACvB,YAAY,CAAC,EAAE,YAAY,GAC1B,YAAY,CAmBd;IAED,YAAY,IAAI,IAAI,CAEnB;IAED,KAAK,IAAI,IAAI,CAEZ;IAED;;;OAGG;IACH,+BAA+B,IAAI,IAAI,CAStC;CACF"}
|
|
1
|
+
{"version":3,"file":"planner-fan-out.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-fan-out.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D,qBAAa,aAAa;;IACxB,QAAQ,CAAC,IAAI,EAAG,SAAS,CAAU;IAKnC,YAAY,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,eAAe,CAAC,EAGvD;IAED,IAAI,IAAI,iBAEP;IAED,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAEjC;IAED,IAAI,OAAO,IAAI,WAAW,EAAE,CAE3B;IAED,IAAI,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,eAAe,CAAC,CAEjD;IAED,mBAAmB,IAAI,gBAAgB,CAEtC;IAED,oBAAoB,CAClB,aAAa,EAAE,MAAM,EAAE,EACvB,UAAU,EAAE,iBAAiB,GAAG,SAAS,EACzC,IAAI,CAAC,EAAE,WAAW,EAClB,YAAY,CAAC,EAAE,YAAY,GAC1B,IAAI,CAgBN;IAED,YAAY,CACV,0BAA0B,EAAE,MAAM,EAClC,aAAa,EAAE,MAAM,EAAE,EACvB,YAAY,CAAC,EAAE,YAAY,GAC1B,YAAY,CAmBd;IAED,YAAY,IAAI,IAAI,CAEnB;IAED,KAAK,IAAI,IAAI,CAEZ;IAED;;;OAGG;IACH,+BAA+B,IAAI,IAAI,CAStC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-fan-out.js","names":["#outputs","#input","#type"],"sources":["../../../../../zql/src/planner/planner-fan-out.ts"],"sourcesContent":["import type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\nimport type {PlannerTerminus} from './planner-terminus.ts';\n\nexport class PlannerFanOut {\n readonly kind = 'fan-out' as const;\n #type: 'FO' | 'UFO';\n readonly #outputs: PlannerNode[] = [];\n readonly #input: Exclude<PlannerNode, PlannerTerminus>;\n\n constructor(input: Exclude<PlannerNode, PlannerTerminus>) {\n this.#type = 'FO';\n this.#input = input;\n }\n\n get type() {\n return this.#type;\n }\n\n addOutput(node: PlannerNode): void {\n this.#outputs.push(node);\n }\n\n get outputs(): PlannerNode[] {\n return this.#outputs;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return this.#input.closestJoinOrSource();\n }\n\n propagateConstraints(\n branchPattern: number[],\n constraint: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'fan-out',\n node: 'FO',\n branchPattern,\n constraint,\n from: from?.kind ?? 'unknown',\n });\n\n this.#input.propagateConstraints(\n branchPattern,\n constraint,\n this,\n planDebugger,\n );\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n const ret = this.#input.estimateCost(\n downstreamChildSelectivity,\n branchPattern,\n planDebugger,\n );\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'fan-out',\n node: 'FO',\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(ret),\n });\n }\n\n return ret;\n }\n\n convertToUFO(): void {\n this.#type = 'UFO';\n }\n\n reset(): void {\n this.#type = 'FO';\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * Fan-out propagates to its input.\n */\n propagateUnlimitFromFlippedJoin(): void {\n if (\n 'propagateUnlimitFromFlippedJoin' in this.#input &&\n typeof this.#input.propagateUnlimitFromFlippedJoin === 'function'\n ) {\n (\n this.#input as {propagateUnlimitFromFlippedJoin(): void}\n ).propagateUnlimitFromFlippedJoin();\n }\n }\n}\n"],"mappings":";;AAUA,IAAa,gBAAb,MAA2B;CACzB,OAAgB;CAChB;CACA,WAAmC,CAAC;CACpC;CAEA,YAAY,OAA8C;EACxD,KAAKE,QAAQ;EACb,KAAKD,SAAS;CAChB;CAEA,IAAI,OAAO;EACT,OAAO,KAAKC;CACd;CAEA,UAAU,MAAyB;EACjC,KAAKF,SAAS,KAAK,IAAI;CACzB;CAEA,IAAI,UAAyB;EAC3B,OAAO,KAAKA;CACd;CAEA,sBAAwC;EACtC,OAAO,
|
|
1
|
+
{"version":3,"file":"planner-fan-out.js","names":["#outputs","#input","#type"],"sources":["../../../../../zql/src/planner/planner-fan-out.ts"],"sourcesContent":["import type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\nimport type {PlannerTerminus} from './planner-terminus.ts';\n\nexport class PlannerFanOut {\n readonly kind = 'fan-out' as const;\n #type: 'FO' | 'UFO';\n readonly #outputs: PlannerNode[] = [];\n readonly #input: Exclude<PlannerNode, PlannerTerminus>;\n\n constructor(input: Exclude<PlannerNode, PlannerTerminus>) {\n this.#type = 'FO';\n this.#input = input;\n }\n\n get type() {\n return this.#type;\n }\n\n addOutput(node: PlannerNode): void {\n this.#outputs.push(node);\n }\n\n get outputs(): PlannerNode[] {\n return this.#outputs;\n }\n\n get input(): Exclude<PlannerNode, PlannerTerminus> {\n return this.#input;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return this.#input.closestJoinOrSource();\n }\n\n propagateConstraints(\n branchPattern: number[],\n constraint: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'fan-out',\n node: 'FO',\n branchPattern,\n constraint,\n from: from?.kind ?? 'unknown',\n });\n\n this.#input.propagateConstraints(\n branchPattern,\n constraint,\n this,\n planDebugger,\n );\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n const ret = this.#input.estimateCost(\n downstreamChildSelectivity,\n branchPattern,\n planDebugger,\n );\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'fan-out',\n node: 'FO',\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(ret),\n });\n }\n\n return ret;\n }\n\n convertToUFO(): void {\n this.#type = 'UFO';\n }\n\n reset(): void {\n this.#type = 'FO';\n }\n\n /**\n * Propagate unlimiting when a parent join is flipped.\n * Fan-out propagates to its input.\n */\n propagateUnlimitFromFlippedJoin(): void {\n if (\n 'propagateUnlimitFromFlippedJoin' in this.#input &&\n typeof this.#input.propagateUnlimitFromFlippedJoin === 'function'\n ) {\n (\n this.#input as {propagateUnlimitFromFlippedJoin(): void}\n ).propagateUnlimitFromFlippedJoin();\n }\n }\n}\n"],"mappings":";;AAUA,IAAa,gBAAb,MAA2B;CACzB,OAAgB;CAChB;CACA,WAAmC,CAAC;CACpC;CAEA,YAAY,OAA8C;EACxD,KAAKE,QAAQ;EACb,KAAKD,SAAS;CAChB;CAEA,IAAI,OAAO;EACT,OAAO,KAAKC;CACd;CAEA,UAAU,MAAyB;EACjC,KAAKF,SAAS,KAAK,IAAI;CACzB;CAEA,IAAI,UAAyB;EAC3B,OAAO,KAAKA;CACd;CAEA,IAAI,QAA+C;EACjD,OAAO,KAAKC;CACd;CAEA,sBAAwC;EACtC,OAAO,KAAKA,OAAO,oBAAoB;CACzC;CAEA,qBACE,eACA,YACA,MACA,cACM;EACN,cAAc,IAAI;GAChB,MAAM;GACN,UAAU;GACV,MAAM;GACN;GACA;GACA,MAAM,MAAM,QAAQ;EACtB,CAAC;EAED,KAAKA,OAAO,qBACV,eACA,YACA,MACA,YACF;CACF;CAEA,aACE,4BACA,eACA,cACc;EACd,MAAM,MAAM,KAAKA,OAAO,aACtB,4BACA,eACA,YACF;EAEA,IAAI,cACF,aAAa,IAAI;GACf,MAAM;GACN,UAAU;GACV,MAAM;GACN;GACA;GACA,cAAc,WAAW,GAAG;EAC9B,CAAC;EAGH,OAAO;CACT;CAEA,eAAqB;EACnB,KAAKC,QAAQ;CACf;CAEA,QAAc;EACZ,KAAKA,QAAQ;CACf;;;;;CAMA,kCAAwC;EACtC,IACE,qCAAqC,KAAKD,UAC1C,OAAO,KAAKA,OAAO,oCAAoC,YAEvD,KACOA,OACL,gCAAgC;CAEtC;AACF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { NoSubqueryCondition } from '../builder/filter.ts';
|
|
2
|
+
import type { PlannerConstraint } from './planner-constraint.ts';
|
|
3
|
+
import type { PlanDebugger } from './planner-debug.ts';
|
|
4
|
+
import type { CostEstimate, JoinOrConnection, PlannerNode } from './planner-node.ts';
|
|
5
|
+
import type { PlannerTerminus } from './planner-terminus.ts';
|
|
6
|
+
/**
|
|
7
|
+
* Represents a simple (non-CSQ) branch of an OR in the planner graph.
|
|
8
|
+
*
|
|
9
|
+
* # Why this node exists
|
|
10
|
+
* Without `PlannerFilter`, the planner would not see simple branches of an
|
|
11
|
+
* OR at all — `processOr` historically only admitted subquery-bearing
|
|
12
|
+
* branches into the graph. That meant the cost model never got to weigh
|
|
13
|
+
* the per-branch filter that the runtime actually pushes down via
|
|
14
|
+
* `FetchRequest.filter` for UFI/UFO branches.
|
|
15
|
+
*
|
|
16
|
+
* # What it does
|
|
17
|
+
* - Stores the simple branch's `NoSubqueryCondition`.
|
|
18
|
+
* - Forwards `propagateConstraints` to its input (typically a `PlannerFanOut`
|
|
19
|
+
* leading back to a `PlannerConnection`).
|
|
20
|
+
* - When the enclosing FanIn is in `UFI` mode, registers the condition at
|
|
21
|
+
* the receiving `PlannerConnection` under the current `branchPattern` via
|
|
22
|
+
* `connection.setPerBranchFilter(...)`. This lets the cost model see the
|
|
23
|
+
* true per-branch filter at fetch time.
|
|
24
|
+
* - In `FI` mode, registration is skipped — at runtime, FI/FO uses a
|
|
25
|
+
* single shared scan of the source for all branches, so per-branch filters
|
|
26
|
+
* would mis-credit the source scan cost.
|
|
27
|
+
*
|
|
28
|
+
* # Cost
|
|
29
|
+
* Pass-through. The filter node itself adds no execution cost; the only
|
|
30
|
+
* effect is at the connection layer (lower estimated rows).
|
|
31
|
+
*
|
|
32
|
+
* # Planner/runtime branch-count mismatch
|
|
33
|
+
* For OR with multiple simple branches and a flipped CSQ (e.g.
|
|
34
|
+
* `or(a=1, b=2, exists(...))`), the planner builds one `PlannerFilter` per
|
|
35
|
+
* simple branch, but at runtime `applyFilterWithFlips` bundles all
|
|
36
|
+
* non-flipped branches into a single source fetch with `(a=1 OR b=2)` —
|
|
37
|
+
* one source scan, not N. In UFI mode the planner therefore models N
|
|
38
|
+
* independent source scans against the runtime's 1, slightly inflating
|
|
39
|
+
* the cost of OR-with-many-simples shapes. Common `or(simple, exists)`
|
|
40
|
+
* shapes have only one simple branch and are unaffected.
|
|
41
|
+
*/
|
|
42
|
+
export declare class PlannerFilter {
|
|
43
|
+
#private;
|
|
44
|
+
readonly kind: 'filter';
|
|
45
|
+
constructor(input: Exclude<PlannerNode, PlannerTerminus>, condition: NoSubqueryCondition | undefined);
|
|
46
|
+
setOutput(node: PlannerNode): void;
|
|
47
|
+
get output(): PlannerNode;
|
|
48
|
+
closestJoinOrSource(): JoinOrConnection;
|
|
49
|
+
propagateConstraints(branchPattern: number[], constraint: PlannerConstraint | undefined, from?: PlannerNode, planDebugger?: PlanDebugger): void;
|
|
50
|
+
estimateCost(downstreamChildSelectivity: number, branchPattern: number[], planDebugger?: PlanDebugger): CostEstimate;
|
|
51
|
+
/**
|
|
52
|
+
* Forward unlimit propagation along the parent chain (toward the source).
|
|
53
|
+
*/
|
|
54
|
+
propagateUnlimitFromFlippedJoin(): void;
|
|
55
|
+
reset(): void;
|
|
56
|
+
/** For debugging. */
|
|
57
|
+
getConditionForDebug(): NoSubqueryCondition | undefined;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=planner-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planner-filter.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-filter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAE9D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,aAAa;;IACxB,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAKlC,YACE,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,eAAe,CAAC,EAC5C,SAAS,EAAE,mBAAmB,GAAG,SAAS,EAI3C;IAED,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAEjC;IAED,IAAI,MAAM,IAAI,WAAW,CAGxB;IAED,mBAAmB,IAAI,gBAAgB,CAEtC;IAED,oBAAoB,CAClB,aAAa,EAAE,MAAM,EAAE,EACvB,UAAU,EAAE,iBAAiB,GAAG,SAAS,EACzC,IAAI,CAAC,EAAE,WAAW,EAClB,YAAY,CAAC,EAAE,YAAY,GAC1B,IAAI,CA4BN;IAED,YAAY,CACV,0BAA0B,EAAE,MAAM,EAClC,aAAa,EAAE,MAAM,EAAE,EACvB,YAAY,CAAC,EAAE,YAAY,GAC1B,YAAY,CAmBd;IAED;;OAEG;IACH,+BAA+B,IAAI,IAAI,CAStC;IAED,KAAK,IAAI,IAAI,CAIZ;IAED,qBAAqB;IACrB,oBAAoB,IAAI,mBAAmB,GAAG,SAAS,CAEtD;CACF"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { assert } from "../../../shared/src/asserts.js";
|
|
2
|
+
import { omitFanout } from "./planner-node.js";
|
|
3
|
+
//#region ../zql/src/planner/planner-filter.ts
|
|
4
|
+
/**
|
|
5
|
+
* Represents a simple (non-CSQ) branch of an OR in the planner graph.
|
|
6
|
+
*
|
|
7
|
+
* # Why this node exists
|
|
8
|
+
* Without `PlannerFilter`, the planner would not see simple branches of an
|
|
9
|
+
* OR at all — `processOr` historically only admitted subquery-bearing
|
|
10
|
+
* branches into the graph. That meant the cost model never got to weigh
|
|
11
|
+
* the per-branch filter that the runtime actually pushes down via
|
|
12
|
+
* `FetchRequest.filter` for UFI/UFO branches.
|
|
13
|
+
*
|
|
14
|
+
* # What it does
|
|
15
|
+
* - Stores the simple branch's `NoSubqueryCondition`.
|
|
16
|
+
* - Forwards `propagateConstraints` to its input (typically a `PlannerFanOut`
|
|
17
|
+
* leading back to a `PlannerConnection`).
|
|
18
|
+
* - When the enclosing FanIn is in `UFI` mode, registers the condition at
|
|
19
|
+
* the receiving `PlannerConnection` under the current `branchPattern` via
|
|
20
|
+
* `connection.setPerBranchFilter(...)`. This lets the cost model see the
|
|
21
|
+
* true per-branch filter at fetch time.
|
|
22
|
+
* - In `FI` mode, registration is skipped — at runtime, FI/FO uses a
|
|
23
|
+
* single shared scan of the source for all branches, so per-branch filters
|
|
24
|
+
* would mis-credit the source scan cost.
|
|
25
|
+
*
|
|
26
|
+
* # Cost
|
|
27
|
+
* Pass-through. The filter node itself adds no execution cost; the only
|
|
28
|
+
* effect is at the connection layer (lower estimated rows).
|
|
29
|
+
*
|
|
30
|
+
* # Planner/runtime branch-count mismatch
|
|
31
|
+
* For OR with multiple simple branches and a flipped CSQ (e.g.
|
|
32
|
+
* `or(a=1, b=2, exists(...))`), the planner builds one `PlannerFilter` per
|
|
33
|
+
* simple branch, but at runtime `applyFilterWithFlips` bundles all
|
|
34
|
+
* non-flipped branches into a single source fetch with `(a=1 OR b=2)` —
|
|
35
|
+
* one source scan, not N. In UFI mode the planner therefore models N
|
|
36
|
+
* independent source scans against the runtime's 1, slightly inflating
|
|
37
|
+
* the cost of OR-with-many-simples shapes. Common `or(simple, exists)`
|
|
38
|
+
* shapes have only one simple branch and are unaffected.
|
|
39
|
+
*/
|
|
40
|
+
var PlannerFilter = class {
|
|
41
|
+
kind = "filter";
|
|
42
|
+
#input;
|
|
43
|
+
#condition;
|
|
44
|
+
#output;
|
|
45
|
+
constructor(input, condition) {
|
|
46
|
+
this.#input = input;
|
|
47
|
+
this.#condition = condition;
|
|
48
|
+
}
|
|
49
|
+
setOutput(node) {
|
|
50
|
+
this.#output = node;
|
|
51
|
+
}
|
|
52
|
+
get output() {
|
|
53
|
+
assert(this.#output !== void 0, "Output not set");
|
|
54
|
+
return this.#output;
|
|
55
|
+
}
|
|
56
|
+
closestJoinOrSource() {
|
|
57
|
+
return this.#input.closestJoinOrSource();
|
|
58
|
+
}
|
|
59
|
+
propagateConstraints(branchPattern, constraint, from, planDebugger) {
|
|
60
|
+
planDebugger?.log({
|
|
61
|
+
type: "node-constraint",
|
|
62
|
+
nodeType: "filter",
|
|
63
|
+
node: "filter",
|
|
64
|
+
branchPattern,
|
|
65
|
+
constraint,
|
|
66
|
+
from: from?.kind ?? "unknown"
|
|
67
|
+
});
|
|
68
|
+
if (this.#condition && from?.kind === "fan-in" && from.type === "UFI") {
|
|
69
|
+
const conn = findParentConnection(this.#input);
|
|
70
|
+
if (conn) conn.setPerBranchFilter(branchPattern, this.#condition);
|
|
71
|
+
}
|
|
72
|
+
this.#input.propagateConstraints(branchPattern, constraint, this, planDebugger);
|
|
73
|
+
}
|
|
74
|
+
estimateCost(downstreamChildSelectivity, branchPattern, planDebugger) {
|
|
75
|
+
const cost = this.#input.estimateCost(downstreamChildSelectivity, branchPattern, planDebugger);
|
|
76
|
+
if (planDebugger) planDebugger.log({
|
|
77
|
+
type: "node-cost",
|
|
78
|
+
nodeType: "filter",
|
|
79
|
+
node: "filter",
|
|
80
|
+
branchPattern,
|
|
81
|
+
downstreamChildSelectivity,
|
|
82
|
+
costEstimate: omitFanout(cost)
|
|
83
|
+
});
|
|
84
|
+
return cost;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Forward unlimit propagation along the parent chain (toward the source).
|
|
88
|
+
*/
|
|
89
|
+
propagateUnlimitFromFlippedJoin() {
|
|
90
|
+
if ("propagateUnlimitFromFlippedJoin" in this.#input && typeof this.#input.propagateUnlimitFromFlippedJoin === "function") this.#input.propagateUnlimitFromFlippedJoin();
|
|
91
|
+
}
|
|
92
|
+
reset() {}
|
|
93
|
+
/** For debugging. */
|
|
94
|
+
getConditionForDebug() {
|
|
95
|
+
return this.#condition;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Walk the input chain (toward the source) looking for the first
|
|
100
|
+
* `PlannerConnection`. The simple branch's filter is meant to land on the
|
|
101
|
+
* connection that the FanOut wraps — for nested AST shapes, that may be
|
|
102
|
+
* several joins above.
|
|
103
|
+
*/
|
|
104
|
+
function findParentConnection(node) {
|
|
105
|
+
switch (node.kind) {
|
|
106
|
+
case "connection": return node;
|
|
107
|
+
case "fan-out": return findParentConnection(node.input);
|
|
108
|
+
case "join": return findParentConnection(node.parent);
|
|
109
|
+
case "fan-in": return;
|
|
110
|
+
case "filter": return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { PlannerFilter };
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=planner-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planner-filter.js","names":["#input","#condition","#output"],"sources":["../../../../../zql/src/planner/planner-filter.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport type {PlannerConnection} from './planner-connection.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanDebugger} from './planner-debug.ts';\nimport {omitFanout} from './planner-node.ts';\nimport type {\n CostEstimate,\n JoinOrConnection,\n PlannerNode,\n} from './planner-node.ts';\nimport type {PlannerTerminus} from './planner-terminus.ts';\n\n/**\n * Represents a simple (non-CSQ) branch of an OR in the planner graph.\n *\n * # Why this node exists\n * Without `PlannerFilter`, the planner would not see simple branches of an\n * OR at all — `processOr` historically only admitted subquery-bearing\n * branches into the graph. That meant the cost model never got to weigh\n * the per-branch filter that the runtime actually pushes down via\n * `FetchRequest.filter` for UFI/UFO branches.\n *\n * # What it does\n * - Stores the simple branch's `NoSubqueryCondition`.\n * - Forwards `propagateConstraints` to its input (typically a `PlannerFanOut`\n * leading back to a `PlannerConnection`).\n * - When the enclosing FanIn is in `UFI` mode, registers the condition at\n * the receiving `PlannerConnection` under the current `branchPattern` via\n * `connection.setPerBranchFilter(...)`. This lets the cost model see the\n * true per-branch filter at fetch time.\n * - In `FI` mode, registration is skipped — at runtime, FI/FO uses a\n * single shared scan of the source for all branches, so per-branch filters\n * would mis-credit the source scan cost.\n *\n * # Cost\n * Pass-through. The filter node itself adds no execution cost; the only\n * effect is at the connection layer (lower estimated rows).\n *\n * # Planner/runtime branch-count mismatch\n * For OR with multiple simple branches and a flipped CSQ (e.g.\n * `or(a=1, b=2, exists(...))`), the planner builds one `PlannerFilter` per\n * simple branch, but at runtime `applyFilterWithFlips` bundles all\n * non-flipped branches into a single source fetch with `(a=1 OR b=2)` —\n * one source scan, not N. In UFI mode the planner therefore models N\n * independent source scans against the runtime's 1, slightly inflating\n * the cost of OR-with-many-simples shapes. Common `or(simple, exists)`\n * shapes have only one simple branch and are unaffected.\n */\nexport class PlannerFilter {\n readonly kind = 'filter' as const;\n readonly #input: Exclude<PlannerNode, PlannerTerminus>;\n readonly #condition: NoSubqueryCondition | undefined;\n #output?: PlannerNode | undefined;\n\n constructor(\n input: Exclude<PlannerNode, PlannerTerminus>,\n condition: NoSubqueryCondition | undefined,\n ) {\n this.#input = input;\n this.#condition = condition;\n }\n\n setOutput(node: PlannerNode): void {\n this.#output = node;\n }\n\n get output(): PlannerNode {\n assert(this.#output !== undefined, 'Output not set');\n return this.#output;\n }\n\n closestJoinOrSource(): JoinOrConnection {\n return this.#input.closestJoinOrSource();\n }\n\n propagateConstraints(\n branchPattern: number[],\n constraint: PlannerConstraint | undefined,\n from?: PlannerNode,\n planDebugger?: PlanDebugger,\n ): void {\n planDebugger?.log({\n type: 'node-constraint',\n nodeType: 'filter',\n node: 'filter',\n branchPattern,\n constraint,\n from: from?.kind ?? 'unknown',\n });\n\n // Only register the per-branch filter when we're actually going to do\n // a per-branch fetch at runtime — i.e., when our enclosing FanIn is in\n // UFI mode. In FI mode, all branches share a single source scan, so\n // applying our filter at the source would drop rows that other\n // branches need.\n if (this.#condition && from?.kind === 'fan-in' && from.type === 'UFI') {\n const conn = findParentConnection(this.#input);\n if (conn) {\n conn.setPerBranchFilter(branchPattern, this.#condition);\n }\n }\n\n this.#input.propagateConstraints(\n branchPattern,\n constraint,\n this,\n planDebugger,\n );\n }\n\n estimateCost(\n downstreamChildSelectivity: number,\n branchPattern: number[],\n planDebugger?: PlanDebugger,\n ): CostEstimate {\n const cost = this.#input.estimateCost(\n downstreamChildSelectivity,\n branchPattern,\n planDebugger,\n );\n\n if (planDebugger) {\n planDebugger.log({\n type: 'node-cost',\n nodeType: 'filter',\n node: 'filter',\n branchPattern,\n downstreamChildSelectivity,\n costEstimate: omitFanout(cost),\n });\n }\n\n return cost;\n }\n\n /**\n * Forward unlimit propagation along the parent chain (toward the source).\n */\n propagateUnlimitFromFlippedJoin(): void {\n if (\n 'propagateUnlimitFromFlippedJoin' in this.#input &&\n typeof this.#input.propagateUnlimitFromFlippedJoin === 'function'\n ) {\n (\n this.#input as {propagateUnlimitFromFlippedJoin(): void}\n ).propagateUnlimitFromFlippedJoin();\n }\n }\n\n reset(): void {\n // No mutable structural state. Per-branch filters are re-registered on\n // every propagation pass, and PlannerConnection clears its\n // #perBranchFilters in its own reset().\n }\n\n /** For debugging. */\n getConditionForDebug(): NoSubqueryCondition | undefined {\n return this.#condition;\n }\n}\n\n/**\n * Walk the input chain (toward the source) looking for the first\n * `PlannerConnection`. The simple branch's filter is meant to land on the\n * connection that the FanOut wraps — for nested AST shapes, that may be\n * several joins above.\n */\nfunction findParentConnection(\n node: Exclude<PlannerNode, PlannerTerminus>,\n): PlannerConnection | undefined {\n switch (node.kind) {\n case 'connection':\n return node;\n case 'fan-out':\n return findParentConnection(node.input);\n case 'join':\n return findParentConnection(node.parent);\n case 'fan-in':\n // Reaching a FanIn while walking up means the simple branch sits\n // above a nested OR/UFI. The connection that matters for *this*\n // simple branch's filter is the one feeding the outer FanOut, but\n // from a nested FanIn we don't have a single input to walk through.\n // Be conservative: don't register, accept that deeply nested OR\n // shapes lose this optimization.\n return undefined;\n case 'filter':\n // Shouldn't occur in graphs built by `processOr` — a PlannerFilter's\n // input is always the FanOut it's a branch of, never another filter.\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,IAAa,gBAAb,MAA2B;CACzB,OAAgB;CAChB;CACA;CACA;CAEA,YACE,OACA,WACA;EACA,KAAKA,SAAS;EACd,KAAKC,aAAa;CACpB;CAEA,UAAU,MAAyB;EACjC,KAAKC,UAAU;CACjB;CAEA,IAAI,SAAsB;EACxB,OAAO,KAAKA,YAAY,KAAA,GAAW,gBAAgB;EACnD,OAAO,KAAKA;CACd;CAEA,sBAAwC;EACtC,OAAO,KAAKF,OAAO,oBAAoB;CACzC;CAEA,qBACE,eACA,YACA,MACA,cACM;EACN,cAAc,IAAI;GAChB,MAAM;GACN,UAAU;GACV,MAAM;GACN;GACA;GACA,MAAM,MAAM,QAAQ;EACtB,CAAC;EAOD,IAAI,KAAKC,cAAc,MAAM,SAAS,YAAY,KAAK,SAAS,OAAO;GACrE,MAAM,OAAO,qBAAqB,KAAKD,MAAM;GAC7C,IAAI,MACF,KAAK,mBAAmB,eAAe,KAAKC,UAAU;EAE1D;EAEA,KAAKD,OAAO,qBACV,eACA,YACA,MACA,YACF;CACF;CAEA,aACE,4BACA,eACA,cACc;EACd,MAAM,OAAO,KAAKA,OAAO,aACvB,4BACA,eACA,YACF;EAEA,IAAI,cACF,aAAa,IAAI;GACf,MAAM;GACN,UAAU;GACV,MAAM;GACN;GACA;GACA,cAAc,WAAW,IAAI;EAC/B,CAAC;EAGH,OAAO;CACT;;;;CAKA,kCAAwC;EACtC,IACE,qCAAqC,KAAKA,UAC1C,OAAO,KAAKA,OAAO,oCAAoC,YAEvD,KACOA,OACL,gCAAgC;CAEtC;CAEA,QAAc,CAId;;CAGA,uBAAwD;EACtD,OAAO,KAAKC;CACd;AACF;;;;;;;AAQA,SAAS,qBACP,MAC+B;CAC/B,QAAQ,KAAK,MAAb;EACE,KAAK,cACH,OAAO;EACT,KAAK,WACH,OAAO,qBAAqB,KAAK,KAAK;EACxC,KAAK,QACH,OAAO,qBAAqB,KAAK,MAAM;EACzC,KAAK,UAOH;EACF,KAAK,UAGH;CACJ;AACF"}
|
|
@@ -4,6 +4,7 @@ import type { PlannerConstraint } from './planner-constraint.ts';
|
|
|
4
4
|
import type { PlanDebugger } from './planner-debug.ts';
|
|
5
5
|
import type { PlannerFanIn } from './planner-fan-in.ts';
|
|
6
6
|
import type { PlannerFanOut } from './planner-fan-out.ts';
|
|
7
|
+
import type { PlannerFilter } from './planner-filter.ts';
|
|
7
8
|
import type { PlannerJoin } from './planner-join.ts';
|
|
8
9
|
import { PlannerSource, type ConnectionCostModel } from './planner-source.ts';
|
|
9
10
|
import type { PlannerTerminus } from './planner-terminus.ts';
|
|
@@ -31,6 +32,7 @@ export declare class PlannerGraph {
|
|
|
31
32
|
fanOuts: PlannerFanOut[];
|
|
32
33
|
fanIns: PlannerFanIn[];
|
|
33
34
|
connections: PlannerConnection[];
|
|
35
|
+
filters: PlannerFilter[];
|
|
34
36
|
/**
|
|
35
37
|
* Reset all planning state back to initial values for another planning pass.
|
|
36
38
|
* Resets only mutable planning state - graph structure is unchanged.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-graph.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAC,aAAa,EAAE,KAAK,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,WAAW,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAAC,CAAC;IAChD,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAAC,CAAC;IACzC,OAAO,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAC,CAAC,CAAC;IACrC,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAC,CAAC,CAAC;IACpC,qBAAqB,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC,CAAC;CAC1E,CAAC;AAkBF,qBAAa,YAAY;;IAQvB,KAAK,EAAE,WAAW,EAAE,CAAM;IAC1B,OAAO,EAAE,aAAa,EAAE,CAAM;IAC9B,MAAM,EAAE,YAAY,EAAE,CAAM;IAC5B,WAAW,EAAE,iBAAiB,EAAE,CAAM;
|
|
1
|
+
{"version":3,"file":"planner-graph.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAC,aAAa,EAAE,KAAK,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,WAAW,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAAC,CAAC;IAChD,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAAC,CAAC;IACzC,OAAO,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAC,CAAC,CAAC;IACrC,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAA;KAAC,CAAC,CAAC;IACpC,qBAAqB,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC,CAAC;CAC1E,CAAC;AAkBF,qBAAa,YAAY;;IAQvB,KAAK,EAAE,WAAW,EAAE,CAAM;IAC1B,OAAO,EAAE,aAAa,EAAE,CAAM;IAC9B,MAAM,EAAE,YAAY,EAAE,CAAM;IAC5B,WAAW,EAAE,iBAAiB,EAAE,CAAM;IACtC,OAAO,EAAE,aAAa,EAAE,CAAM;IAE9B;;;;;OAKG;IACH,kBAAkB,SAMjB;IAED;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,aAAa,CAQjE;IAED;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAIrC;IAED;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/B;IAED;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAE3C;IAED;;;;OAIG;IACH,oBAAoB,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI,CAMtD;IAED;;;OAGG;IACH,YAAY,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,MAAM,CAGhD;IAED;;;;;;;;OAQG;IACH,uBAAuB,IAAI,SAAS,CAUnC;IAED;;;;;;;;OAQG;IACH,uBAAuB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAK9C;IAiFD;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,IAAI,CA8HvD;CACF"}
|