Conversation
This was referenced Oct 2, 2026
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 2, 2026 18:32
5686cf7 to
d9223e3
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
2 times, most recently
from
October 2, 2026 19:40
bffe654 to
74cb049
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 2, 2026 19:40
d9223e3 to
26da80b
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 2, 2026 21:14
74cb049 to
49d558c
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 2, 2026 21:14
26da80b to
51a5484
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 2, 2026 21:22
49d558c to
34a7fad
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 2, 2026 21:22
51a5484 to
b58f2b4
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 2, 2026 21:25
34a7fad to
7357d5b
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 2, 2026 21:25
b58f2b4 to
8fb402b
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 2, 2026 21:56
7357d5b to
eec2e77
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
2 times, most recently
from
October 3, 2026 02:31
2535016 to
e55e580
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
2 times, most recently
from
October 3, 2026 02:39
268f020 to
b310b0e
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 02:39
e55e580 to
397e56a
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 14:57
b310b0e to
82ea050
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 14:57
397e56a to
c58ce05
Compare
This was referenced Oct 3, 2026
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 16:07
82ea050 to
7adc4e9
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
2 times, most recently
from
October 3, 2026 16:08
fb30215 to
d133a1d
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 16:08
7adc4e9 to
7e6d76f
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 16:33
d133a1d to
d61bf7b
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 16:33
7e6d76f to
8a633ee
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 17:02
8a633ee to
06eb517
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 17:02
d61bf7b to
130fc54
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 17:11
06eb517 to
69e4337
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
2 times, most recently
from
October 3, 2026 17:23
4cd558c to
7f65a80
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 17:23
69e4337 to
543314d
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 17:29
7f65a80 to
569b845
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
2 times, most recently
from
October 3, 2026 17:40
0af6d2e to
1f36004
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
2 times, most recently
from
October 3, 2026 19:32
00c2776 to
2918b78
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
2 times, most recently
from
October 3, 2026 19:45
2f7c76f to
e633197
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 19:45
2918b78 to
21b014f
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 19:47
e633197 to
230b940
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 19:47
21b014f to
008b0d0
Compare
zzylol
marked this pull request as draft
October 3, 2026 20:02
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 20:09
230b940 to
545ecc7
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 20:09
008b0d0 to
e3febef
Compare
zzylol
force-pushed
the
stack/528-04-sql
branch
from
October 3, 2026 20:48
545ecc7 to
c2c329b
Compare
zzylol
force-pushed
the
stack/528-05-promql
branch
from
October 3, 2026 20:48
e3febef to
ca8b839
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.
Problem: PromQL and MetricsQL still lower to the mixed legacy graph, not to #511's operators and scalar expressions
#511 §1 states the problem: the current representation "mixes query-plan operators and scalar expressions", so "an expression can be placed where a table input is expected and fail only when the plan is checked". #511 §2.3 "Composition without a bridge" and §3.3 "PromQL semantic mapping" give the required PromQL shapes:
§3.3 adds:
up{job="api"}isTimeRange(Instant)over a scan andup[5m]isTimeRange(Range).up > 0is aFilter.up > bool 0is aProjectwithCase(Compare(...), 1.0, 0.0). Vector/vector comparisons keepvector_matchand setreturn_bool. Per-sample math is aProjectwith a scalarFunctionCallwhose "scalar parameters can be expressions; do not require constant-onlyAggIntent::Mathpayloads". §3.4 lists the gaps that "the frontend must reject … until it is supplied", including native-histogram samples, dynamic aggregate/sampling parameters and fill modifiers. §4 requires the §2.3 examples to "need noPromqlScalarBridgeor equivalent constant-wrapper node".#509 §0 "Language-specific frontends" sets the same rule from the planner side: a construct "that cannot be represented faithfully is rejected", and the frontend keeps series identity, evaluation timing and missing-data semantics.
Before this PR, the PromQL frontend (
crates/frontend-promql/src/promql.rs) and the MetricsQL frontend (crates/frontend-metricsql/src/lib.rs) build only the legacyQueryExpr. On the same queries:QueryExpr(before)2PromqlScalarBridgeleaftime()EvalTimestampoperator leafup * 2BinaryOp{Mul, up, PromqlScalarBridge(2)}BinaryOpinput-upBinaryOp{Mul, up, PromqlScalarBridge(-1)}up > bool 0BinaryOp{CompareBool, up, PromqlScalarBridge(0)}abs(up)Aggregate{[Math(Abs)]}round(up, scalar(sum(other)))Math::Round { to_nearest }needs a constantupvsup[1s]TimeRange{1s}histogram_quantile(0.9, native_latency)(no catalog entry, nole)Quantilehistogram_count(v)Aggregate{[HistogramCount]}This PR covers the PromQL rows of #511 §2.3 and §3.3, and the representable subset of MetricsQL. It also rejects the §3.4 PromQL gaps listed above. It does not change the planner callers: they keep using the legacy entry points until the cutover. It does not upgrade the parser or add runtime execution.
Proposed method
This is the frontend stage (#509 §0). Both frontends get a parallel
unifiedmodule. It builds the name-basedUnresolvedOp/UnresolvedScalartree fromasap-frontend-common(added in #539) and resolves it withresolve_root/resolve_scalar_rootintoOperatorNode/ScalarExpr. No legacyQueryExpris built and no conversion between graphs is added.PromQL (
crates/frontend-promql/src/unified/):lower_promql_query_workloadrequiresQueryLanguage::PromQL, runsworkload.validate(), and readsdata_ingestion_intervalatnow_ms. Expired or future evidence fails. Each entry is lowered with its ownrequirements.accuracy.target().Native, the query is rejected (UnsupportedFeature).value_type()isScalar, the query becomesQueryRoot::Scalarthroughlower_scalar. Otherwise it becomesQueryRoot::Operatorthroughwalk. A scalar-typed expression at an operator position is an error, so a scalar never becomes an operator node.lower_scalar). Constant sub-expressions fold toLiteral(Float64). Otherwise-x→Negative, scalar arithmetic →Arithmetic,time()→EvalTimestamp,pi()→Literal(π),scalar(v)→PromqlScalarFromVector(<operator plan of v>), anda < bool b→Case(Compare → 1.0, else 0.0). A scalar comparison withoutboolis rejected. All scalar nodes carryExprSemantics::Promql.PromqlScalarOp { child, scalar, op, scalar_left, return_bool }. The resolver turns it into aFilter(comparison withoutbool, sample values kept) or aProject(arithmetic, or comparison withbool). An operator read inside the scalar (for examplescalar(sum(up))) stays a visible child of that node.BinaryOp { operator: BinaryOperator { kind, vector_match, .. }, return_bool, lhs, rhs }.CompareBoolis no longer used to encodebool.abs,round,clamp*, …), unary minus and calendar functions becomePromqlMap. The resolver projects the sample throughFunctionCall { name: "promql_<fn>", args }(orNegative) and keeps the full series identity. Extra arguments go throughlower_scalar, so they can be dynamic (time() - 1,scalar(...)).round(v)gets a default1.0. Unary minus keeps the metric name; math and calendar functions drop it.m{…}→TimeRange { range: data_ingestion_interval, kind: Instant }over the scan.m{…}[w]→TimeRange { range: w, kind: Range }.offset/@wrap the scan inTimeShift.topk/bottomk→Limit { n: Some(k), partition_by: none }overSort { value, partition_by: keys }.topkovercount_over_time/sum_over_timestays the heavy-hitterAggIntent::TopK.histogram_quantile. A declaredClassicBucket, or classic-bucket evidence (by (le), a_bucketmetric, anlematcher), gives exactHistogramQuantile. A declaredRawSamplesgives the sketchableQuantilewith the entry's accuracy. Mixed declarations,Native, and undeclared metrics without bucket evidence are rejected.histogram_count/sum/avg/stddev/stdvar/fractionare rejected.limitk/limit_ratioparameters, non-constantmin_of/max_of, and fill modifiers.MetricsQL (
crates/frontend-metricsql/src/unified/mod.rs) has the same structure as the legacy MetricsQL lowerer, with these changes: a bare number literal becomesQueryRoot::Scalar(Literal). A number literal operand becomesPromqlScalarOp. Unary minus becomesPromqlScalarOp(* -1). Matchers carryExprSemantics::Promql. Range windows becomeTimeRange(Range). Everything it did not support before is still rejected.The old entry points stay so each stack layer builds on its own. The
unifiedmodules are documented as "promoted to the root API at planner cutover".Key code interfaces
PromQL,
crates/frontend-promql/src/unified/mod.rs:unified/error.rs:unified/histogram.rs:MetricsQL,
crates/frontend-metricsql/src/unified/mod.rs:Usage:
The output types (
QueryRoot,OperatorNode,ScalarExpr,NonASAPOp) and the unresolved tree (UnresolvedOp::PromqlMap,UnresolvedOp::PromqlScalarOp,resolve_root,resolve_scalar_root) come from earlier PRs in the stack. This PR only uses them. The crate-private entry point isPromqlLowerer::lower_query_with_ingestion_interval(query, accuracy, interval) -> Result<QueryRoot>. Test helpers live intests/unified_support.rs.Fields
PromQL lowering functions
workload&PlanningWorkloadlanguage = PromQL(elseWrongLanguage) and passvalidate()(elseInvalidWorkload). Each entry fromquery_workload.entries()is lowered with its ownrequirements.accuracy.target().data_workload.data_ingestion_intervalis required.now_msu64value_at(now_ms)), elseInvalidWorkload(UnavailableDataIngestionInterval). Expiry is inclusive.histogramsHistogramCatalog*_query_workload*Vec<QueryRoot>Scalarfor scalar-typed queries,Operatorotherwise.lower_promql_workload*Vec<Rc<OperatorNode>>UnsupportedFeature("scalar root: use lower_promql_query_workload").PromqlErrorInvalidWorkload(WorkloadError)now_ms.Parse(String)promql-parserrejected the string.UnsupportedFunction(String)UnsupportedAggregateOp(String)UnsupportedFeature(String)histogram_quantile, a scalar at an operator position, a scalar root on the vector-only API, fill modifiers, and similar.MissingArgument(String)InvalidParameter(String)boolor a fractionaltopkk.WrongLanguage(String)Convert(ResolveDAGError)asap-frontend-commonfailed.HistogramKind(what a client declares for a metric)is_sketchablehistogram_quantileresultClassicBucketfalseHistogramQuantile(cumulativelebuckets)NativefalseRawSamplestrueQuantilewith the entry's accuracy target. This is an explicit extension, not standard PromQL histogram semantics.HistogramCatalog: a map from metric name toHistogramKind.new()is empty.with(metric, kind)adds or replaces one entry (builder style).kind_of(metric)looks one up.is_empty()reports an empty catalog. If no metric in the argument is declared, only classic-bucket evidence is accepted.MetricsQL functions and
MetricsqlErrorquery: &straccuracy: AccuracyTargetCount(count_over_time),Cardinality(count) andQuantileintents.MetricsqlExprmetricsql_parser::ast::Expr.parse_metricsqlreturns it;canonical_metricsqlreturns itsto_string().lower_metricsql_queryresultQueryRoot::Scalar(Literal)for a bare number,QueryRoot::Operatorotherwise.lower_metricsqlrejects the scalar case.Parse(String)UnsupportedFeature(String)@, subquery step,keep_metric_names, vector-matching modifiers,if/ifnot/default, …).Resolve(String)Examples
End to end:
up * scalar(sum(up))Input:
lower_promql_query_workloadon a PromQL workload with this one query,Exactaccuracy and a 1 s ingestion interval (fromtests/unified_scalar_design.rs,scalar_plan_dependencies_remain_visible).The parser types the root as an instant vector, so
walkis used.walk_binarysees that the right operand is scalar-typed. The left sideuplowers toTimeRange{1s, Instant}overScan{up}. The right side goes throughlower_scalar:scalar(...)→PromqlScalarFromVector(<plan of sum(up)>).The result is
PromqlScalarOp { op: Mul, scalar_left: false, return_bool: false }. The resolver adds series identity to the open PromQL schema and builds aProjectwhosevaluecolumn isArithmetic(Mul, sample, PromqlScalarFromVector(..)).Output:
The test asserts
node.children().len() == 2: the plan read by the scalar is a visible dependency, not hidden inside the expression.Before/after on the #511 examples
From
tests/unified_scalar_design.rs,tests/unified_promql_lowering.rsandtests/unified_promql_conformance.rs:2,time(),scalar(sum(up)) + 1,1 < bool 2,-time()QueryRoot::Scalar, type-checks with an empty schemastandalone_scalars_are_expressionsup * 2Project,value = Arithmetic(Mul, sample, Literal(2.0)), pluspromql_drop_metric_name; series identity andtime_indexkeptarithmetic_projects_the_sample_and_preserves_full_identity_and_timeup > 0,0 < upFilter; output schema equals the input schemanon_bool_comparisons_keep_vector_samples_even_with_scalar_on_leftup > bool 0ProjectwithCasebool_comparison_projects_zero_or_one-upProjectwithNegative; schema unchanged (metric name kept)unary_minus_preserves_identityabs(up),round(up, scalar(sum(other))),clamp(up, time() - 1, time()),year(up),hour()ProjectwithFunctionCall;validate_structurepassespointwise_functions_are_typed_scalar_projectionssum by (job) (rate(http_requests_total[5m]))Aggregate{by [2], [Sum]}overAggregate{[Rate]}overTimeRange{5m}sum_by_over_rate_groups_the_outer_suma / on(host) bBinaryOpwithvector_match { kind: On, labels: ["host"] }binary_op_with_on_grouping[w]of the same lengthTimeRange{Instant}vsTimeRange{Range}instant_and_range_selectors_of_equal_length_stay_distincthistogram_quantile(0.9, sum by (le) (rate(..._bucket[5m])))HistogramQuantileheuristic_baseline_is_unchanged_without_a_cataloghistogram_quantile(0.9, foo_bucket)withfoo_bucket: RawSamplesQuantiledeclared_raw_extension_and_native_gap_override_the_heuristichistogram_quantile(0.9, latency_seconds)withClassicBucketHistogramQuantiledeclared_classic_bucket_fixes_the_false_negativeRejected on purpose:
histogram_quantile(0.9, native_latency), no catalogUnsupportedFeature(needs classic-bucket evidence)heuristic_baseline_is_unchanged_without_a_catalogfoo_bucketdeclaredNative(even a bare selector)UnsupportedFeaturedeclared_raw_extension_and_native_gap_override_the_heuristichistogram_count(v)and other native accessors,histogram_fractionUnsupportedFeaturenative_histogram_accessors_are_explicit_gaps,histogram_fraction_is_an_explicit_gaplimitk(scalar(foo), m),limit_ratio(time() % 17 / 17, m),limitk(NaN, m)dynamic_and_non_finite_sample_params_are_rejected1 < 2,time() > 0(scalar comparison withoutbool)InvalidParameter)scalar_comparison_without_bool_is_rejecteda + fill(0) band other fill modifiersUnsupportedFeaturefill_modifiers_are_rejected_not_ignoredmin_of/max_ofnon_constant_min_of_max_of_is_rejected__GAPOut of scope
lower_promql_workload/lower_metricsqluntil the cutover. The temporary parallel entry points are removed at the end of the stack.quantile(scalar(q), up), fill modifiers, request-context durations such asstep()). They are rejected, not supported.data_ingestion_interval, as in the legacy lowerer. docs: define unified operators and SQL/PromQL scalar boundaries #511 §2.1 says theInstantlookback "is not the ingestion interval". This PR adds theInstantkind but does not change the horizon.Scan(noTimeRange(Instant)), as in the legacy MetricsQL lowerer.Stack and validation
Revised logical foundation 4/5 · Previous: #539 · Next: #561 · Tracker: #528
Order: #567 → #560 → #537 → #539 → #540 → #561. Splits the PromQL and MetricsQL lowering portion of #528. Restacked onto phase-free logical export: test helpers export
LogicalASAPDAGwithout lifecycle assignment.Validation: workspace all-target check; PromQL and MetricsQL tests, including the new scalar-design and conformance suites; workspace all-target/all-feature Clippy with warnings denied. Complete first-five validation: 1,961 workspace tests/doctests, formatting and workspace/all-target/all-feature Clippy pass.
🤖 Generated with Claude Code