Repository navigation
Conversation
5 tasks done
zzylol
force-pushed
the
refactor/1207-memo
branch
3 times, most recently
from
November 9, 2026 04:40
fe0ff33 to
8095a51
Compare
…1207 PR 2b) GroupProperties gains column_lineage (one ColumnLineage per output column, derived once from the input groups'). Lineage and column_sign are derived together by one per-operator column layout (stages/column_properties.rs, which replaces column_sign.rs): a copied column keeps its lineage and sign, a computed one is Computed with the operator's sign rule. Chains compute window_end and their state and keep their keys' lineage; a TimeRange or TimeShift over a chain, and TimeShift { at: End | Start } above a selector, pass lineage through; a merge of a chain's selected windows has the chain build's layout. Summary coverage names a selection's column by its SourceColumn lineage (the one SourceColumn type, beside Source); ColumnIdentity is deleted. A predicate on a Computed column, or on one of two columns of one lineage, is not lifted; the regrouping key check compares lineage. Schema fields and the IR Project lose their table qualifier; the front ends bind qualified names with their own scope while lowering. The requalification patches (with_aggregate_qualifiers, node.rs) are deleted.
zzylol
force-pushed
the
refactor/1207-lineage
branch
from
November 9, 2026 06:16
e2b5456 to
58549f2
Compare
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
Schema fields carried a table qualifier (
Field::table), and every operator rule had to keep it right: aProjectreplaced it with its own qualifier, an aggregate copied it onto its keys, and two patches put it back where rules lost it (with_aggregate_qualifiersin Pass 1, the requalification inOperatorNode::with_new_children/validate_structure). Summary coverage named a selection's column by(table, name)(ColumnIdentity), so a rename made the same source column look like another column, andMemo::add_candidate's exact schema compare rejected a readout renamed to an aggregate's column because its qualifier differed.The concepts doc (
memo-groups-properties-requirements.md, section 3, "Column lineage") replaces the qualifier by a group property: each output column's lineage, the source column its values are copied from, orComputed. A front end binds a qualified name such asf.srcwith its own scope while it lowers, and the IR never needs the qualifier again.PR 2 added
column_sign, a second per-column group property, with its own copy of every operator's column layout (aggregates, chains, readouts). Lineage needs the same layout, so this PR derives both from one.What
GroupProperties::column_lineage: Vec<ColumnLineage>, one entry per output column in schema order, derived once inderive_group_properties.stages/column_sign.rs(PR 2) becomesstages/column_properties.rs:output_column_propertiesreturns each output column's lineage and sign together. A column that copies an input column keeps both; a computed one isComputed, with the sign the operator's rule gives (PR 2's rules, unchanged).GroupPropertieskeeps the two fields the doc lists,column_lineageandcolumn_sign.SourceColumn. PR 2'sSourceColumn { source, column }besideSource(ir/operator/operator_properties.rs) is the only one: it is the declared-sign key (PR 2),ColumnLineage::SourceColumn(SourceColumn), and the key ofSelectionBox::columns.ColumnIdentityis deleted. It stays besideSourcebecause the data workload's sign declarations and Stage 0 already use it there, and lineage and coverage import it from there; the earlier copy inir/properties/column_lineage.rsis deleted with that module.SummaryTumblingWindowChainhaswindow_end(column 0)Computed, its keys their input's lineage, its stateComputed; aTimeRangeorTimeShiftover a chain passes the chain's lineage through; theSummaryMergeof a chain's selected windows has the chain build's layout (keys, then state).TimeShift { at: End | Start }above a selector (PR 2's lowering of@ end()/@ start()) passes the selector's lineage through.Computedcolumn is not lifted and stays inunlifted_operators; neither is one on a column whose lineage another column of theSummaryAgg's input shares. The regrouping merge's key check compares lineage, and the timestamp column aDisjointInTimemerge and a window selection constrain is named by its lineage.table; the IRProjectlosesqualifier.Field::with_tableandSchema::column_id_qualifiedare deleted;resolve_column_refresolves aColumnRef::Qualifiedby its unique name.frontend-common's resolver, used by SQL, PromQL and MetricsQL) bind qualified names with their own scope: each resolved operator carries the qualifier of each output column next to its node. The SQL front end setsUnresolvedOp::Scan::qualifierto the table or alias.with_aggregate_qualifiers(Pass 1) and the requalification innode.rsandtumbling_window_chain.rs.Memo::add_candidatestays strict (exact schema compare).asap-primitive-schema.md§4.2.2 (worked example G1–G10 and "Which column a condition is on") uses lineage:latency.job ∈ {api},latency.ts ∈ (t − 60 s, t],t.region ∈ {us}; §2's table and the listings ofField,Schema,SelectionBoxandSourceColumnfollow. The concepts doc's lineage table gains the chain-selection,@ end()/@ start()andSummaryMergerows and says lineage and sign share one layout (docs/memo-group-model 1662257).Deleted
Field::table,Field::with_tabletypes/src/ir/schema/mod.rsGroupProperties::column_lineage; front-end scopeSchema::column_id_qualifiedScope::resolve(front end, private)NonASAPOp::Project::qualifiertypes/src/ir/operator/non_asap.rsUnresolvedOp::Project::qualifieronlyOperatorNode::with_new_childrenandvalidate_structuretypes/src/ir/operator/node.rswith_aggregate_qualifiersstage1-pass1-per-computation-candidatesColumnIdentity { table, name }and its test-supportnewtypes/src/ir/properties/summary_coverage.rsSourceColumn(besideSource)column_identitysource_column(by lineage)build_input_schemabuild_input(the input group, for its schema and lineage)stages/column_sign.rs:output_column_signand its per-operator layout (non_asap_signs,asap_signs,aggregate_signs,state_signs,readout_signs,project_signs,agreed)types/src/stages/stages/column_properties.rs: one layout for lineage and signtopk_{k}output-name rulenon_asap.rs, the sign/lineage rule,agg_intent.rstopk_output_name(ir/schema/topk.rs)qualified_field_serde_roundtrip, the qualified case ofpartitions_over_matches_keys_by_column, the qualifier asserts ofrebuilding_preserves_only_explicit_naming_overridesHow
output_column_properties(operator, output, declared, columns_of)matches the operator once and returns aColumnProperties { lineage, sign }per output column. AScangivesSourceColumn { source: table or metric, column: field name }with its declared sign;Filter,Sort,Limit,Dedup,TimeRange,TimeShiftand the other row-selecting PromQL operators pass their input's columns; aProjectcolumn item copies its input column, an expression item isComputedwithexpression_sign; anAggregateand aSummaryAggfollowaggregate_output_schema's layout (abykey copied, awithoutreduction the kept labels, a per-series reduction every column but the value; measures and the stateComputedwithmeasure_sign/ the update's sign); a chain is its build's layout without the build's time column, after a computedwindow_end; a readout copies its state's plain columns; aJoinconcatenates;SetOp,Concatand aSummaryMergekeep what their inputs agree on; aBinaryOpkeeps the left side's columns (and, foror/group_right, what both sides agree on), computes the value, and copies the labels the match adds from the right side.derive_group_propertiesreads a group's columns withcolumns_of_groupand splits the result intocolumn_lineageandcolumn_sign. Each rule gives one entry per output column (debug_assert_eq!).of_aggregatereads the lineage of the aggregate's input (a group's stored lineage, or a temporary build's, derived by the sameoutput_column_propertiesfrom its inputs').residuallifts a conjunct only whensource_columnnames its column: aSourceColumnlineage no other column shares; the selection is keyed by it.regroupedmaps each key to the build input's column of equal lineage.resolvereturns a privateBound { node, qualifiers }. Qualified references resolve in aScope { schema, qualifiers }: a column with that qualifier and name first, else the unique name. Qualifiers follow the old field rules: aScanits own, a pass-through operator its input's, aProjectits alias or none, an aggregate'sbykeys their input's, aJoinleft then right, aSetOpthe left side's; PromQL operators have none.Major algorithm changes
Summary coverage: which conjuncts are lifted.
(table, name).SELECT source … FROM (SELECT f.src AS source FROM flows f)named the column(none, source), and one grouped byf.srcnamed it(f, src): the same column looked like two. A conjunct on a computed column (a rate value) was lifted although no source column names it.residual). 2. Lift one when it is a value set or interval on one column whose(table, name)is unique in the aggregate's input. 3. Key the selection by(table, name).SourceColumnunique among the input's columns. 3. Key the selection by that source column.regions). AComputedcolumn has no source to compare by, so it stays in the definition, which merges only equal definitions. Tests:types/tests/column_lineage.rsandmemo_summary_coverage.rs.O(columns)per group (the input groups' columns are copied once per rule).source_columncounts namesakes as before,O(columns)per conjunct.rate(m[5m]) > 0no longer liftsvalue ∈ (0, ∞):valueis computed byrate. A top-k readout's and a population evaluation's columns areComputed(conservative).Key code interfaces
crates/types/src/stages/memo.rscrates/types/src/ir/operator/operator_properties.rscrates/types/src/ir/properties/summary_coverage.rscrates/types/src/stages/column_properties.rs(wascolumn_sign.rs; crate-private)crates/types/src/ir/schema/mod.rscrates/types/src/ir/operator/non_asap.rscrates/frontend-common/src/unresolved.rscrates/planner/src/lib.rsUsage
A SQL query, lowered and put in the memo; then Pass 1 (PR 3) adds an HLL readout as a candidate of the aggregate's group. Pass 1 is
todo!()until PR 3, soan_hll_readout_renamed_to_the_aggregate_column_is_its_candidate(types/tests/column_lineage.rs, passing at this head) builds the groups Stage 0 and Pass 1 would add (helpersscan,filter,equals,hll,column_ofin that file):Before this PR
Field::tableright; coverage named columns by(table, name), so a rename broke column identity, and a readout renamed to an aggregate's column failedadd_candidate's exact schema compare.column_signhad its own copy of every operator's column layout.After this PR
column_lineage, derived withcolumn_signfrom one layout; coverage, its merge checks and its regrouping key check read it. Renamed columns keep their identity; the tworegions of a join stay apart; a computed column is never named in a selection; a chain's keys, its window selections and@ end()/@ start()keep their lineage.add_candidate, and names columns bySourceColumn.Wire format
The exported
SchemaJSON no longer has a"table"key on fields, and a selection key (SourceColumn) serializes as{source, column}(only in a materialized view's coverage, which istodo!()until PR 6). Older plan files still deserialize (a field's unknown keys are ignored). No consumer reads a qualifier (the executor matches columns by name), so nothing is derived at the export boundary; PR 6 owns the wire version.Pending approval
New or changed public items and behaviour not in the doc or #1207 (conservative choices):
ColumnLineagelives inasap_types::stages(stages/memo.rs, besideValueSign), not in a module of its own (ir::properties::column_lineageis deleted). Definition above. Example:use asap_types::stages::{ColumnLineage, ValueSign};thenassert_eq!(memo.groups()[g.0 as usize].group_properties.column_lineage[0], ColumnLineage::SourceColumn(SourceColumn { source: "flows".into(), column: "src".into() })).SourceColumnstays besideSource(feat(stage0, types): memo hash-consing, group properties (column_sign, depends_on_evaluation_time, aggregate_coverage), @ end()/@ start() above their subtree (#1207 PR 2) #1230 Q1), withpubfields and no constructor (the 11-07 copy hadpub(crate)fields and a test-supportnew). Example:SourceColumn { source: "latency".into(), column: "job".into() }is the lineage of everyjobcolumn copied from the serieslatencyand the key of a selectionlatency.job ∈ {api}.SourceColumn(asap_planner::SourceColumn) in place ofColumnIdentity, since it keysSelectionBox::columns.BinaryOpmatch adds: copied from the right side's column together with its lineage, where PR 2 gaveUnknown; foror/group_right, a left label keeps the sign both sides agree on (PR 2 kept the left side's). One layout copies a column's lineage and sign together, so the two cannot disagree. Example: ina * on(job) group_left(team) b, the output's last columnteamhas lineageb.teamandb'steamsign (PR 2: alwaysUnknown). Labels are strings, whose sign isUnknownunless declared, so no current contract reads a different value. The value column's sign rules are PR 2's, unchanged.UnresolvedOp::Scan::qualifier: Option<String>;ResolveError::Ambiguousmessage "matches several columns";resolve_column_refignores a qualifier.Verification
types/tests/column_lineage.rs), one per doc example plus the chain rules:an_hll_readout_renamed_to_the_aggregate_column_is_its_candidate(example 1): lineage of G1–G5 andadd_candidateaccepting the renamed readout.a_rename_keeps_lineage_and_the_rollup_key_check_compares_it(example 2).join_columns_of_one_name_have_distinct_lineage(example 3).scan_predicates_lift_and_their_states_merge(example 4, inmemo_summary_coverage.rs, assertinglatency.job).a_predicate_on_a_computed_column_stays_unlifted.a_chain_computes_window_end_and_state_and_its_window_selections_pass_lineage_through: chain[Computed, latency.job, Computed];TimeShiftandTimeRangeover it the same; the merge of the selected windows[latency.job, Computed](under the 11-07 rule this merge failed the lengthdebug_assert).a_time_shift_at_end_or_start_above_a_selector_passes_its_lineage_through.a_qualified_group_key_binds_its_join_side(frontend-sql).memo_group_properties.rs, stage0'sa_scan_reads_the_declared_column_signs) pass unchanged over the shared layout.cargo fmt --all --check;cargo clippy --workspace --all-features --tests -- -D warnings: clean.crate_graph(integration-tests) passes.Test status
cargo test -p <crate> --all-features --no-fail-fastper crate at 58549f2. Compared by test name with PR 2 (#1230, 8095a51: 1121 passed / 189 failed / 9 ignored): no test that passes on PR 2 fails here. +7 passed are the new tests (types: the sixcolumn_lineagetests; frontend-sql:a_qualified_group_key_binds_its_join_side), −1 the deletedqualified_field_serde_roundtrip. Every failure is atodo!()of a later PR, with the same causes and counts as PR 2.Result::unwrap()on anErrvalue: Error("invalid type: sequence, expected string or map", line: 0, column: 0): 1