Repository navigation
feat(planner): compose candidate logical ASAP DAGs from Pass 1 per-computation candidates - #575
Merged
Merged
Conversation
This was referenced Oct 3, 2026
zzylol
force-pushed
the
stack/528-05b-local-alternatives
branch
from
October 5, 2026 06:21
517cd55 to
488c6eb
Compare
zzylol
force-pushed
the
stack/509-c2-stage1-workload
branch
2 times, most recently
from
October 8, 2026 16:47
10f0989 to
4c700d6
Compare
zzylol
force-pushed
the
stack/528-05b-local-alternatives
branch
from
October 10, 2026 14:32
14c6b88 to
c84eb6f
Compare
zzylol
force-pushed
the
stack/509-c2-stage1-workload
branch
2 times, most recently
from
October 11, 2026 17:11
0150354 to
db2f3c4
Compare
zzylol
added a commit
that referenced
this pull request
Oct 12, 2026
Summary candidates are unsized (LocalCandidate::Summary carries only the algorithm); sizing happens after Pass 2 sharing. Every pruned candidate is recorded on its target with a PruneReason (RequiresExact, NullableAvgInput). Each measure of an aggregate with a realization besides exact execution is its own target; input containing ASAP operators is rejected. avg is rewritten as exact sum/count accumulator states. The Example 1 test runs Q1 as exact, and the dev doc places composition in Pass 1. - One exact option per target: the exact accumulator (ExactAccumulator, or AvgAsSumCount for avg) when it can be built, otherwise plain Exact (NullableAvgInput prune, or no accumulator form). The plain form would duplicate "accumulator, not materialized"; Stage 2/3 decide materialization and sharing. - Per-measure filters and HAVING keep every candidate. The target records the measure's filter (applied to the state as SummaryAgg.filter) and a HavingPlacement for the aggregate's having or a Filter directly above it (the SQL frontend's HAVING form): BelowAggregate when it reads only group keys (pushed below as a row predicate), else AboveOutput (a Filter above the finalize/estimate output; approximate candidates carry guarantee = None). Composition (#575) applies them; PruneReason::FilteredAggregate is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
force-pushed
the
stack/528-05b-local-alternatives
branch
from
October 12, 2026 20:30
7c8ae7d to
9d5ab8f
Compare
zzylol
added a commit
that referenced
this pull request
Oct 12, 2026
Summary candidates are unsized (LocalCandidate::Summary carries only the algorithm); sizing happens after Pass 2 sharing. Every pruned candidate is recorded on its target with a PruneReason (RequiresExact, NullableAvgInput). Each measure of an aggregate with a realization besides exact execution is its own target; input containing ASAP operators is rejected. avg is rewritten as exact sum/count accumulator states. The Example 1 test runs Q1 as exact, and the dev doc places composition in Pass 1. - One exact option per target: the exact accumulator (ExactAccumulator, or AvgAsSumCount for avg) when it can be built, otherwise plain Exact (NullableAvgInput prune, or no accumulator form). The plain form would duplicate "accumulator, not materialized"; Stage 2/3 decide materialization and sharing. - Per-measure filters and HAVING keep every candidate. The target records the measure's filter (applied to the state as SummaryAgg.filter) and a HavingPlacement for the aggregate's having or a Filter directly above it (the SQL frontend's HAVING form): BelowAggregate when it reads only group keys (pushed below as a row predicate), else AboveOutput (a Filter above the finalize/estimate output; approximate candidates carry guarantee = None). Composition (#575) applies them; PruneReason::FilteredAggregate is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 12, 2026
…ter Pass 1 input checks LocalLogicalTarget.having is now one HavingEntry (consumer Filter, predicate, placement) per HAVING. A consumer Filter's key-only predicate is pushed below only when every consumer of the aggregate applies it, and a global aggregate's HAVING always stays above the output. Only the adjacent form is recorded; value predicates elsewhere get guarantee None via #575, multi-level push-down is #664. Inputs with assigned materialization are rejected too (AssignedPhysicalAnnotation). Approximate accuracy needs epsilon and delta in (0, 1), and InvalidAccuracy names the root and measure. The avg nullability check runs only for avg. Tests cover SQL HAVING placement, a shared aggregate, a global constant HAVING and #509 Example 1 Q2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
force-pushed
the
stack/528-05b-local-alternatives
branch
from
October 12, 2026 22:55
9d5ab8f to
8eccd53
Compare
This was referenced Oct 12, 2026
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
Combinations of approximate values may read summaries: each scalar reference is planned against the entry's accuracy target again. Composing the guarantee of such a combination is left to #575 and Stage 3's accuracy model. This reverts commit c44ecda. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
Front ends build identical references as separate nodes and planning merges them, so pointer-distinct references left the rebuilt scalar with one reference but two scalar plans, failing the contract. References are now distinct by structure. Document that each reference keeps its summary candidates and that the combined guarantee is composed later (#669, #575). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
force-pushed
the
stack/528-05b-local-alternatives
branch
from
October 13, 2026 01:43
8eccd53 to
d18125b
Compare
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
Summary candidates are unsized (LocalCandidate::Summary carries only the algorithm); sizing happens after Pass 2 sharing. Every pruned candidate is recorded on its target with a PruneReason (RequiresExact, NullableAvgInput). Each measure of an aggregate with a realization besides exact execution is its own target; input containing ASAP operators is rejected. avg is rewritten as exact sum/count accumulator states. The Example 1 test runs Q1 as exact, and the dev doc places composition in Pass 1. - One exact option per target: the exact accumulator (ExactAccumulator, or AvgAsSumCount for avg) when it can be built, otherwise plain Exact (NullableAvgInput prune, or no accumulator form). The plain form would duplicate "accumulator, not materialized"; Stage 2/3 decide materialization and sharing. - Per-measure filters and HAVING keep every candidate. The target records the measure's filter (applied to the state as SummaryAgg.filter) and a HavingPlacement for the aggregate's having or a Filter directly above it (the SQL frontend's HAVING form): BelowAggregate when it reads only group keys (pushed below as a row predicate), else AboveOutput (a Filter above the finalize/estimate output; approximate candidates carry guarantee = None). Composition (#575) applies them; PruneReason::FilteredAggregate is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…ter Pass 1 input checks LocalLogicalTarget.having is now one HavingEntry (consumer Filter, predicate, placement) per HAVING. A consumer Filter's key-only predicate is pushed below only when every consumer of the aggregate applies it, and a global aggregate's HAVING always stays above the output. Only the adjacent form is recorded; value predicates elsewhere get guarantee None via #575, multi-level push-down is #664. Inputs with assigned materialization are rejected too (AssignedPhysicalAnnotation). Approximate accuracy needs epsilon and delta in (0, 1), and InvalidAccuracy names the root and measure. The avg nullability check runs only for avg. Tests cover SQL HAVING placement, a shared aggregate, a global constant HAVING and #509 Example 1 Q2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
* refactor(planner): plan one shared unified workload DAG Cut today's planner over to the unified OperatorNode IR. The planner calls the unified frontends (`asap_frontend_*::unified`) and returns OperatorNode / QueryRoot; ParsedWorkload keeps scalar roots; Pass 1 (ASAPStrategies), the identical-sub-DAG sharing, selection, DAG assembly and lifecycle costing all run on OperatorNode; and PlanOutput exposes the whole workload DAG. Planned summary states derive their coverage from the node (#646). The legacy physical planner read the planner's QueryExpr output, so it is removed here together with its expression compiler and raw sources; every caller uses the native compiler from #541 (`unified_physical_planner`, `unified_sources`), which reads the PhysicalASAPDAG export. Operator names follow the evaluation vocabulary (readout -> evaluation). The MetricsQL frontend input carries the planning clock and lowers through `lower_metricsql_query_workload`, so bare selectors get the workload's ingestion-interval lookback (#540). The legacy frontend modules and the QueryExpr/SummaryNode IRs stay compiled but are no longer re-exported from post_asap. The next PR moves the unified modules to the crate roots; a later one removes the legacy IRs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * feat(planner): plan and export standalone scalar queries A workload entry whose root is a standalone scalar (`scalar(quantile(0.99, rate(x[5m])))`, `scalar(sum(up)) + 1`) was copied through unplanned. Per #511 §2.3 it has no query node and reads operator sub-DAGs by reference; per operator-sharing §3 those sub-DAGs get the same validation and accuracy assessment as operator roots. MajorPass now searches, selects, assembles and lifecycle-plans each distinct operator sub-DAG a scalar root reads, against the scalar's entry and accuracy target, alongside the operator roots, and rebuilds the scalar over the selected sub-DAGs. PlanOutput::scalar_plans holds their lifecycle plans. PhysicalASAPDAG gains `scalar_roots`: each standalone scalar query as a `ScalarExpr<PhysicalASAPNodeId>` keyed by its position in the batch, so query i is the scalar root keyed i, or else the next of `roots`. Validation checks positions and treats scalar references as roots for existence, cycles and reachability. Wire version 9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): plan non-pass-through scalar references exact A standalone scalar hands its entry's accuracy target to its operator reference only when it passes that one reference through (`scalar(v)`, or arithmetic/comparisons with literals). Otherwise each reference is planned exact: independently approximate references leave a combination such as a difference of two quantiles unbounded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): check scalar plans in the contract; remove dead code check_contract now verifies that `scalar_plans` holds one plan per distinct reference of each scalar root, in order and keyed by its entry. The scalar_roots doc says keys are batch positions, equal to entry indices because the contract requires dense indices. A validation test covers duplicate and descending scalar-root keys. The never-constructed VectorScalar/ScalarVector operand modes and the unused ParsedWorkload::new/entries are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): lower scalar(v) in Project/Filter expressions as scalar inputs `a / scalar(b)` and `a > scalar(b)` failed costing with UnsupportedQueryOperator. A Project or Filter now takes one PromqlVectorToScalar input per distinct operator reference its expressions read, after its row input; its statistics carry one `scalar_inputs` edge per such input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): reuse a top-k only over the same partitions TopKLimitReuseStrategy offered a global top-10 as the source of a per-job top-5. It now requires equal `partition_by`, and the maintained population recognizer requires the limit to partition like its sort. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * refactor(planner): tell logical rewrites apart by provenance `is_logical_rewrite` inferred a logical rewrite from the replacement's shape (no guarantee, no ASAP operator). It is now `ReplacementSubDAG::is_logical_rewrite`, read from the candidate's provenance (LogicalRewrite, CseShare, CseRecompute, AccuracyReconciliation); the free function is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): return an error when search input already holds ASAP operators The search entry points asserted that no workload root contains an ASAP operator. They now return `SearchError::AsapInput`; MajorPass reports it as `OptimizeError::Search`, and the explanation entry points pass it on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): walk operator nodes read by scalar expressions The explanation report located only relational children, so a target read through `scalar(...)` in a predicate or projection had no breadcrumb; it now visits every operator input, as target discovery does. `frontier_from_timing` also puts an ingestion-time node a standalone scalar root reads on the frontier, like an operator root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(ir): do not promote a filtered count ranking to a heavy-hitter TopK The IR canonicalize pass ignored measure filters, so `COUNT(*) FILTER (WHERE ...)` or `COUNT(col)` over a nullable column ranked by ORDER BY ... LIMIT became a heap sketch over every row. It now keeps Sort + Limit for a filtered measure (the legacy #466 guard), and the keyed heap-sketch input rule refuses a filtered inner aggregate. The legacy does_not_promote_a_filtered_count_ranking test is ported to the IR and through the SQL front end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * Revert "fix(planner): plan non-pass-through scalar references exact" Combinations of approximate values may read summaries: each scalar reference is planned against the entry's accuracy target again. Composing the guarantee of such a combination is left to #575 and Stage 3's accuracy model. This reverts commit c44ecda. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): rank top-k only when the Limit partitions like its Sort A global Limit over a partitioned Sort keeps k rows overall, not k per group, so neither the heavy-hitter promotion nor the current-series heap candidates apply to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(ir): export one ScalarRef edge per distinct referenced node Cost lowering reads a node referenced several times from one operator's expressions once; the export now matches, deduping by node id (one id per pointer). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * test(planner): cover scalar inputs, top-k partitions, scalar plan contract, provenance Project/Filter with two scalar references and one read twice lower and cost; the maintained population requires the Limit to partition like its Sort; the keyed heap input refuses a filtered count; check_contract rejects scalar plans out of order, under another entry, or extra; a guarantee-less summary realization is not a logical rewrite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * test(planner): a difference of two scalar quantiles reads summaries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): plan structurally identical scalar references once Front ends build identical references as separate nodes and planning merges them, so pointer-distinct references left the rebuilt scalar with one reference but two scalar plans, failing the contract. References are now distinct by structure. Document that each reference keeps its summary candidates and that the combined guarantee is composed later (#669, #575). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * test(planner): reject a per-job Limit over a global Sort; doc cleanups Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): read exact-state query roots through their finalize MajorPass assembled roots with assemble_selected_dag, so a root whose selected candidate is an exact state came out as a bare SummaryAgg. The lifecycle plan now finalizes its root after costing (the finalize wraps the shared state, so sharing and cost evidence stay), scalar references included since scalar(v) reads values. Query-root callers and tests use assemble_selected_query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
Summary candidates are unsized (LocalCandidate::Summary carries only the algorithm); sizing happens after Pass 2 sharing. Every pruned candidate is recorded on its target with a PruneReason (RequiresExact, NullableAvgInput). Each measure of an aggregate with a realization besides exact execution is its own target; input containing ASAP operators is rejected. avg is rewritten as exact sum/count accumulator states. The Example 1 test runs Q1 as exact, and the dev doc places composition in Pass 1. - One exact option per target: the exact accumulator (ExactAccumulator, or AvgAsSumCount for avg) when it can be built, otherwise plain Exact (NullableAvgInput prune, or no accumulator form). The plain form would duplicate "accumulator, not materialized"; Stage 2/3 decide materialization and sharing. - Per-measure filters and HAVING keep every candidate. The target records the measure's filter (applied to the state as SummaryAgg.filter) and a HavingPlacement for the aggregate's having or a Filter directly above it (the SQL frontend's HAVING form): BelowAggregate when it reads only group keys (pushed below as a row predicate), else AboveOutput (a Filter above the finalize/estimate output; approximate candidates carry guarantee = None). Composition (#575) applies them; PruneReason::FilteredAggregate is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…ter Pass 1 input checks LocalLogicalTarget.having is now one HavingEntry (consumer Filter, predicate, placement) per HAVING. A consumer Filter's key-only predicate is pushed below only when every consumer of the aggregate applies it, and a global aggregate's HAVING always stays above the output. Only the adjacent form is recorded; value predicates elsewhere get guarantee None via #575, multi-level push-down is #664. Inputs with assigned materialization are rejected too (AssignedPhysicalAnnotation). Approximate accuracy needs epsilon and delta in (0, 1), and InvalidAccuracy names the root and measure. The avg nullability check runs only for avg. Tests cover SQL HAVING placement, a shared aggregate, a global constant HAVING and #509 Example 1 Q2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
force-pushed
the
stack/528-05b-local-alternatives
branch
2 times, most recently
from
October 13, 2026 02:57
8817d84 to
d3e39fe
Compare
…DAG documents Replays #575's devtools commits on the #561 Pass 1 API: the frontends section is the frontends' workload DAG and logical_asap_optimization holds one composed whole-workload candidate per choice of a per-computation candidate for every target, as flat DAGs, capped by --max-candidates. Example 1 has three candidates (one exact option per target), so the cap test cuts at two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
… targets Composition substitutes every #561 Pass 1 choice. A measure filter becomes SummaryAgg.filter (the realization rule #608 carried, with its filtered-output nullability in ASAPOp schemas); a key-only HAVING that every consumer applies becomes a Filter below the state (SummaryAgg.filter would keep the dropped groups); a value HAVING stays a Filter above the output. avg divides its finalized sum and count accumulators, and the measures of a multi-measure aggregate are substituted one by one and joined back on the group keys. ScalarExpr::map_columns renumbers a predicate's columns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
…antees Every node of a composed candidate gets a guarantee: exact over exact inputs, a summary estimate's sized family bound over exact input, and None for anything computed from an approximate value, including a value HAVING or any other predicate at any depth (#669 defines composition; plan selection's accuracy model decides). The Example 1 fixture shows them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
… values A comparison of approximate operands (quantile(0.99, …) > quantile(0.5, …)) now gets the same BinaryOp candidate over summary operands as arithmetic does, so no combination loses its summary candidates. Automatic selection still needs evidence about the candidate's output: a root guarantee that meets the target, which composition (#669) supplies, or the deployment's empirical output-level accuracy model (plan selection, #576). Certified inputs are not enough, since a difference or ratio of them can be unbounded, so such candidates are kept but not selected under the built-in models. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
The composition of per-computation candidates into whole-workload candidates is Algorithm 1's SUBSTITUTE; its helpers are named so. The module doc describes it and states that summaries are sized here per measure because Pass 2 sharing is not implemented yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
A heap sketch's bound is per item frequency, not Top-K membership, and a Count-Min bound assumes non-negative weights. A composed Top-K estimate, and a Count-Min estimate over weights not proven non-negative, now get no guarantee; plan selection's accuracy model decides them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
topk(3, sum by (job) (rate(m[5m]))) listed heap candidates that composition could not substitute: the closed child [job, value] has no series identity. Its item is now the tuple of labels outside the partition, as in the legacy current-series and counter rules. stage_pipeline records a candidate it cannot compose with its error instead of aborting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
An estimate or accumulator names its value after the operation (quantile_0_99 became quantile, avg sum, a per-series count value count). A single-measure substitution is now renamed to the aggregate's columns, as join_measures already did; a renaming projection keeps its child's guarantee. The logical_asap_optimization test compares column names too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
force-pushed
the
stack/509-c2-stage1-workload
branch
from
October 13, 2026 04:12
ec00b3c to
03ebf0e
Compare
A subquery in a substituted aggregate's measure filter or own HAVING now reads its rewritten plan, and a summary state's guarantee requires every input, its filter's subqueries included, to be exact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
As its summary estimate is: no row passing the filter gives NULL. The filtered quantile queries join the SQL candidate composition test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
A PromQL BinaryOp matches Utf8 labels: it merges NULL and '' groups and rejects other key types. For SQL rows the finalized sum and count are joined on the group keys (NULL keys match) and divided as SQL, NULL where the count is 0 (a filtered-empty group, an empty global input). PromQL keeps the BinaryOp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…#667 materialization Stage 2 takes query roots, scalar ones included (#542), times every sub-DAG with Materialization::NotMaterialized (#667) and keeps the guarantee Stage 1 composed on every node it implements; a MaintainPopulation is no longer rejected, since it now runs at query time when not materialized. Stage 3 accepts a query's output when its root guarantee meets the target, or when the deployment's accuracy model shows this candidate's output does (AccuracyModel::output_guarantee, none by default). A certified estimate inside the plan is not enough: a difference of two quantile estimates stays listed as invalid, with a reason, under the built-in models. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…#575 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…bound #575 gives Top-K and signed Count-Min estimates guarantee None, so the CountSketch+heap candidate of the top-k fixture no longer carries an analytical guarantee: plan selection rejects it for missing evidence rather than for missing the target. The "misses target" case now uses a quantile summary planned at a looser target, and the per-node cost test admits the CountSketch+heap candidate through deployment evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
The count state behind avg-as-sum/count and SQL count read the sample value, which for SQL rows is `value` or the only numeric column: avg(c) divided by count(value), count(*) counted a nullable column, and with several numeric columns the composed DAG was invalid. The runtime skips NULL updates, so: - avg's count reads the averaged column (non-null, or the candidate is pruned); - SQL count reads a column its filter proves non-null (count(col)), else a non-nullable column; with none it is Unsupported; - an unresolvable state input is Unsupported, and a composed DAG is validated before it is returned. PromQL counts still read the sample value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
marked this pull request as ready for review
October 13, 2026 05:24
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…#667 materialization Stage 2 takes query roots, scalar ones included (#542), times every sub-DAG with Materialization::NotMaterialized (#667) and keeps the guarantee Stage 1 composed on every node it implements; a MaintainPopulation is no longer rejected, since it now runs at query time when not materialized. Stage 3 accepts a query's output when its root guarantee meets the target, or when the deployment's accuracy model shows this candidate's output does (AccuracyModel::output_guarantee, none by default). A certified estimate inside the plan is not enough: a difference of two quantile estimates stays listed as invalid, with a reason, under the built-in models. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…#575 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…bound #575 gives Top-K and signed Count-Min estimates guarantee None, so the CountSketch+heap candidate of the top-k fixture no longer carries an analytical guarantee: plan selection rejects it for missing evidence rather than for missing the target. The "misses target" case now uses a quantile summary planned at a looser target, and the per-node cost test admits the CountSketch+heap candidate through deployment evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…#667 materialization Stage 2 takes query roots, scalar ones included (#542), times every sub-DAG with Materialization::NotMaterialized (#667) and keeps the guarantee Stage 1 composed on every node it implements; a MaintainPopulation is no longer rejected, since it now runs at query time when not materialized. Stage 3 accepts a query's output when its root guarantee meets the target, or when the deployment's accuracy model shows this candidate's output does (AccuracyModel::output_guarantee, none by default). A certified estimate inside the plan is not enough: a difference of two quantile estimates stays listed as invalid, with a reason, under the built-in models. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…#575 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…bound #575 gives Top-K and signed Count-Min estimates guarantee None, so the CountSketch+heap candidate of the top-k fixture no longer carries an analytical guarantee: plan selection rejects it for missing evidence rather than for missing the target. The "misses target" case now uses a quantile summary planned at a looser target, and the per-node cost test admits the CountSketch+heap candidate through deployment evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
zzylol
added a commit
that referenced
this pull request
Oct 13, 2026
…r candidate logical ASAP DAGs (#576) * feat(planner): #509 Stage 2 physical candidates and Stage 3 plan selection (MVP) Stage 2 implements one logical candidate physically: exact Aggregate{[TopK]} becomes Limit(Sort) per group, summaries stay build -> estimate, every node runs at query time, and the timed roots export as one PhysicalASAPDAG. Stage 3 is the only stage that computes cost. It rejects candidates whose summary misses its query's accuracy target (accuracy model, analytical guarantee) or uses Count-Min over weights not proven non-negative, prices every valid candidate node by node with analytical_cost (illustrative statistics), selects the cheapest and reports the rest as costlier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(devtools): stage_pipeline emits all four stages for Example 1 Add stage2_physical_asap (no cost) and stage3_selection (costs, selected, every other candidate rejected as invalid or costlier) per the v2 contract. PromQL rows carry the series identity from Stage 0, as runtime state needs. Regenerate tools/dag-viewer/examples/planner-layering-example1.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(planner): read Operator<NodeId> payloads in Stage 2/3 after the export removal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): Stage 2/3 on #575's composed candidates, query roots and #667 materialization Stage 2 takes query roots, scalar ones included (#542), times every sub-DAG with Materialization::NotMaterialized (#667) and keeps the guarantee Stage 1 composed on every node it implements; a MaintainPopulation is no longer rejected, since it now runs at query time when not materialized. Stage 3 accepts a query's output when its root guarantee meets the target, or when the deployment's accuracy model shows this candidate's output does (AccuracyModel::output_guarantee, none by default). A certified estimate inside the plan is not enough: a difference of two quantile estimates stays listed as invalid, with a reason, under the built-in models. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * test(devtools): regenerate the four-stage Example 1 fixture on the new #575 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * docs(planner): name Stage 2/3 by the planner-layering stage terms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(devtools): prune a logical candidate Stage 2 cannot implement, with its reason A Stage 2 error no longer fails the whole candidate set: stage_pipeline lists that candidate under stage2_physical_asap.pruned and plans the rest. Full materialization enumeration is left to #604. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): read empirical output accuracy from deployment evidence in Stage 3 The output-level hook moves from AccuracyModel to AccuracyEvidenceProvider: AccuracyModel stays theoretical, Stage 1 composes root guarantees with the built-in model, and deployment evidence enters only in Stage 3. A scalar root that is exactly scalar(v) inherits v's guarantee; any other scalar expression over an approximate operand has none until #669. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * refactor(planner): name Stage 2/3 code and pipeline keys after the stage names physical_candidates -> physical_asap_optimization (stage2_physical -> optimize_physical_asap, Stage2Error -> PhysicalASAPOptimizationError), stage3_select -> select_plan, and the stage_pipeline keys stage2_physical_asap -> physical_asap_optimization and stage3_selection -> plan_selection. The fixture is regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * test(planner): plan selection tests after Top-K estimates lost their bound #575 gives Top-K and signed Count-Min estimates guarantee None, so the CountSketch+heap candidate of the top-k fixture no longer carries an analytical guarantee: plan selection rejects it for missing evidence rather than for missing the target. The "misses target" case now uses a quantile summary planned at a looser target, and the per-node cost test admits the CountSketch+heap candidate through deployment evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): exact top-k sorts by the ranked measure in physical optimization Rank a one-measure grouped child by its measure (the SQL heavy-hitter and PromQL `topk(k, sum by ..)` shapes) and per-series rows by the sample value, instead of looking up a `value` column. A node reading the top-k gets a Project restoring the logical [keys.., topk_k] columns over the rows. An Aggregate{[TopK]} with other measures, a filter or HAVING now fails, so its candidate is pruned with the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(devtools): stage_pipeline emits plan_selection when selection fails With no valid candidate the document lists the rejections with a null `selected` (and the Stage 2 pruned list as before); any other selection error is reported under `error`, instead of failing the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * docs(planner): plan selection checks Count-Min only under a target; note MVP costing limits Reword the Count-Min test to the decided behavior and test that a query without a target admits the Count-Min candidate. Document that a cost error aborts selection (only from bad declared rates) and that binary, join and concat nodes are priced from their first input only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): exact top-k ranks by the measure of a composed Stage 1 child After Pass 1 composition a top-k reads its one-measure aggregate as Project(FinalizeExactAccumulator(SummaryAgg)) or Project(SummaryEstimate(..)), not as an Aggregate, so ranking fell back to the sample value: a key named `value` was sorted on, and an integer or multi-column key pruned the candidate. Look through renaming Projects, Filters, finalize and estimate to the SummaryAgg or Aggregate; a grouped one ranks by its last column. The SQL test now composes every Pass 1 choice under exact and approximate targets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): exact top-k roots output the logical [keys.., topk_k] columns An exact top-k root returned its child's raw rows (Example 1's P1 leaked `ts`, `value` and `$promql_series_identity`), while summary candidates and the frontend give `[keys.., topk_k]`. The restoring Project now sits over the Limit at a root too, so one implementation serves roots and consumers. `topk_k` carries the ranked value's type and nullability, not the frontend's Utf8 placeholder. Regenerate the Example 1 fixture: P1 gains the root Project (node 12). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * fix(planner): charge a NotMaterialized shared node once per consuming query Plan selection prices each node once per computation: a materialized node once, a NotMaterialized node once per query (or materialized node) that reaches it, not counting through a materialized ancestor. Per-node costs carry the multiplier. Also list the per-entity case among the exact top-k shapes Stage 2 cannot rewrite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW * docs(planner): nodes below a materialized node are charged once for it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The planner-layering design doc defines the output of Stage 1 (logical ASAP-aware optimization) as candidate logical ASAP DAGs: each is one logical ASAP DAG for the whole workload. Pass 1 (#561, merged) only produces per-computation candidates (
ComputationChoicesperComputationTarget). It builds no DAG, so nothing after Pass 1 has a whole-workload candidate to work on, and there is no way to look at what the frontends and Stage 1 produce.What
compose_logical_candidate(inventory, choice)inasap-aware-mapping/src/per_computation_candidates.rs: Algorithm 1's SUBSTITUTE. It takes one Pass 1 per-computation candidate per target and builds one whole-workload candidate logical ASAP DAG.FinalizeExactAccumulatoroutput naming fix.stage_pipelinedevtool, which writesstage0_query_language_frontendsandstage1_logical_asap_optimizationdocuments (read by its devtools test; the viewer support is feat(dag-viewer): redesign the viewer around the stage pipeline #574), plus the regeneratedtools/dag-viewer/examples/planner-layering-example1.jsonfixture.SummaryAgg.filter.How
choice[i]indexestargets[i].choices.candidates. The targets (measures) of one aggregate are grouped. An aggregate whose chosen candidates are allExactis kept as is. Otherwise each measure is substituted on its own over the aggregate's input:ExactAccumulator:SummaryAggwith the exact family →FinalizeExactAccumulator.Summary:SummaryAgg→SummaryEstimate.AvgAsSumCount: the finalizedsumandcountaccumulators, divided. For PromQL this is a vectorBinaryOp; for SQL the two states are joined on the group keys (NULL keys match) and divided, withCASE count = 0 THEN NULLfor a filtered or global avg.Exact(or a measure that is not a target): a single-measureAggregate.Then:
quantile_0_99no longer becomesquantile, noravgbecomesum). A renaming projection keeps its child's guarantee.SummaryAgg.filter, so a group with no matching row stays.ASAPOpschemas mark a filtered quantile and a filtered SUM/MIN/MAX as nullable, and the exactAggregatenow marks a filtered quantile nullable too, so every candidate has the same nullability. Subqueries inside a measure filter or HAVING are substituted like any other target.BelowAggregate) is pushed below the state as aFilter, only when every consumer applies it. Its key columns are renumbered to the input's (ScalarExpr::map_columns), and the consumerFilterit came from is dropped. It is not aSummaryAgg.filter, which would keep the dropped groups.AboveOutput) stays aFilterabove the output; its guarantee isNonewhen the measure is approximate (exact candidates stay exact).composed_guarantee): exact over exact inputs. ASummaryEstimateover exact input gets the sized family's bound, except for Top-K (a heap sketch bounds item frequencies, not membership) and for Count-Min over weights not proven non-negative, which getNone. Anything computed from an approximate value, including value HAVING and any predicate at any depth, getsNone. Composing guarantees through operators is Compose accuracy guarantees through arithmetic and comparisons over approximate values #669.replacement.rs):is_supported_exact_binarynow accepts comparisons (Compare,CompareBool) as well as arithmetic, soquantile(0.99, …) > quantile(0.5, …)keeps its summary candidates. Certified inputs are not enough for automatic selection, since a difference or ratio of them can be unbounded. Such candidates stay, but are selected automatically only with output-level evidence: a root guarantee that meets the target, or the deployment's empirical output-level accuracy model. The plan-selection part is feat(planner): physical ASAP-aware optimization and plan selection for candidate logical ASAP DAGs #576.topk(3, sum by (job) (rate(m[5m])))) ranks the tuple of its labels outside the partition, as the legacy current-series and counter rules do.FinalizeExactAccumulatornamed its outputstate, so a consumer reading the sample value (for examplesum by (job)over a finalized per-seriesrate) found novaluecolumn. It now takes the name of the aggregate it realizes.stage_pipeline(crates/devtools/src/bin/stage_pipeline.rs) writes anasap-stage-pipeline/v1document.stage0_query_language_frontendsis the frontends' workload DAG.stage1_logical_asap_optimizationholds one composed candidate per choice of per-computation candidate for every target, in enumeration order. Every combination is enumerated (no enumeration cap); the document shows the first--max-displayedcandidates (default 64) plus the selected one, withcombinationsandcapped(whether some are not shown). A candidate that cannot be composed is recorded with itserror, and the run continues.Key code interfaces added / modified
crates/asap-aware-mapping/src/per_computation_candidates.rscrates/asap-aware-mapping/src/replacement.rscrates/types/src/ir/scalar.rscrates/types/src/ir/asap.rs,non_asap.rscrates/devtools/src/bin/stage_pipeline.rs(new binary){ // Document format tag. "format": "asap-stage-pipeline/v1", // The input queries with their language, text and requirements. "workload": { "queries": [{ "id": "q1", "language": "promql", "text": "...", "requirements": { "accuracy": "exact" } }] }, // The frontends' workload DAG, one root per query. "language_specific_frontends": { "dag": { /* FlatDag */ } }, // Pass 1 whole-workload candidates, one per choice combination, in enumeration order. "logical_asap_optimization": { // Total number of combinations (product of per-target candidate counts). "combinations": 4, // True when combinations exceed --max-candidates and the list is truncated. "capped": false, // Each candidate has a `dag`, or an `error` when composition could not build it. "candidates": [{ "id": "L1", "label": "...", "dag": { /* FlatDag */ } }, { "id": "L2", "label": "...", "error": "..." }] } }Usage:
Before this PR
Pass 1 lists per-computation candidates for each target, and nothing turns them into candidate logical ASAP DAGs. A comparison of approximate values loses its summary candidates. A finalized exact accumulator's column is called
state. Top-K over rows with series identity, or over a closed PromQL child, has no usable summary item. Nothing shows the frontends' DAG or the Stage 1 output.After this PR
A choice of one per-computation candidate per target composes into a whole-workload candidate logical ASAP DAG, except for a few shapes that return
Unsupportedand are recorded with their error bystage_pipeline(Top-K with no label outside its partition orwithout, a multi-measure aggregate withwithoutkeys, a non-Float64 per-series avg, and SQLcount(*)over rows with no column known to be non-null). Composition never returns an invalid DAG: every composed root passesvalidate_structure, otherwise the choice returns an error. A composed candidate has the queries' output column names, types and nullability, a sized summary per measure, and a theoretical guarantee on every node.stage_pipelinewrites the frontends' DAG and these candidates. Stage 2 (physical ASAP-aware optimization) and Stage 3 (plan selection) consume the candidates in #576.Evidence
crates/integration-tests/tests/stage1_logical_asap_optimization.rs: every candidate of a set of SQL and PromQL queries composes into a valid DAG with the query's output columns (names, types, nullability), including filtered quantiles; subqueries in measure filters are substituted; SQL avg executes like the original (NULL and '' groups stay separate). Count states:sql_avg_counts_its_own_input,sql_count_star_counts_a_non_null_column,sql_count_star_without_a_non_null_column_is_unsupported,sql_count_of_a_nullable_column_counts_its_values; HAVING subqueries:own_having_subquery_is_substituted. Top-K estimates carry no frequency bound while a unit-count CMS estimate keeps its bound. Top-K over a closed PromQL child ranks its labels.crates/asap-aware-mapping/tests/per_computation_candidates.rs: substitution of the chosen target, per-measure sizing, filters and HAVING, avg as sum/count, a multi-measure join, and guarantees.crates/planner/tests/summary_sharing.rs:combined_vector_quantiles_keep_unselected_summary_candidates.crates/devtools/tests/stage_pipeline.rs: the Example 1 document is valid and the committed fixture is current, the display cap is recorded and keeps the selection, and an uncomposable candidate is recorded with its error. Example 1 has three candidates (one exact option per target).Verification
cargo fmt --all --check: OKcargo clippy --workspace --all-targets -- -D warnings: OKcargo test --workspace: 1591 passed, 0 failed, 2 ignoredtest_render: OKverify_metricsql_external_consumer.sh: OKArchitectural decisions
per_computation_candidates.rs. Its helpers are named after Algorithm 1's SUBSTITUTE.SummaryAgg.filter. A key-only HAVING becomes aFilterbelow the state only when every consumer applies it. A value HAVING stays above, with guaranteeNone.Later changes: #577 removed the enumeration cap (
--max-candidates) in favor of the display cap--max-displayed; #674 renamed the document keys and test file by stage number and name (stage0_query_language_frontends,stage1_logical_asap_optimization,stage1_logical_asap_optimization.rs) and made composition part of the planner pipeline (run_stage1_to_stage3/Stage1ToStage3Pass, #675 names); #675 renamed the candidatelabeltodisplay_name.Limitations and follow-up
count(col)countscolunder itsIS NOT NULLfilter, SQLcount(*)counts a non-nullable column (preferring Float64; a non-Float64 pick composes but fails physical compile, since the runtime update accepts only Float64). With no such column (e.g. over a derived table whose columns are all nullable), SQLcount(*)isUnsupported. Follow-up: count a projected literal1.0column under theSummaryAgg, which removes the column search. Filtered summary updates (e.g.count(v)over a nullablev) compose but have no native runtime implementation yet.Stack: tracker #528 · main → #575 → #576 → #577 → …
Human review — do not complete with an agent