diff --git a/internal/chsql/vector_set_op.go b/internal/chsql/vector_set_op.go index 7d157796..de4191c7 100644 --- a/internal/chsql/vector_set_op.go +++ b/internal/chsql/vector_set_op.go @@ -60,12 +60,32 @@ func (e *emitter) emitVectorSetOp(s *chplan.VectorSetOp) error { return err } + // Canonicalise each arm's projection to (MetricName, Attributes, + // TimeUnix, Value) regardless of whether the inner plan exposes + // MetricName directly. Without this normalisation, mixing arms with + // different column shapes — e.g. a canonical-shape Project + // (MetricName, Attributes, TimeUnix, Value) on one side and a matrix + // RangeWindow (Attributes, anchor_ts, TimeUnix, Value) on the other — + // produces a UNION ALL where positional column type unification fails + // with NO_COMMON_TYPE (String vs Map). Three-arm `A or B or C` + // recursion hits this: the inner `(A or B)` already projects the + // canonical shape (MetricName-first), while a sibling `increase(C)[ + // 5m]` arm in range mode projects Attributes-first; the outer UNION + // then tries to coalesce String and Map at column position 0. See + // the docstring on emitVectorSetOp for the original 2-arm motivation; + // the per-arm canonical projection covers both that case and the + // matrix-shape mismatch surfaced by the dashboard sweep + // (otelcol-observability "refused / send-failed / dropped" panels). + leftArm := vectorSetOpCanonicalArmFrag(s, s.Left, leftFrag) + rightArm := vectorSetOpCanonicalArmFrag(s, s.Right, rightFrag) + switch s.Op { case chplan.VectorSetAnd: // SELECT MetricName, Attributes, TimeUnix, Value - // FROM (SELECT * FROM () WHERE IN (SELECT DISTINCT FROM ())) + // FROM (SELECT MetricName, Attributes, TimeUnix, Value FROM () WHERE IN (SELECT DISTINCT FROM ())) inner := NewQuery(). - From(leftFrag). + Select(vectorSetOpOutputCols(s)...). + From(leftArm). Where(setOpInSubqueryFrag(s.Match, s.AttributesColumn, rightFrag, true /*in*/)) outer := NewQuery(). Select(vectorSetOpOutputCols(s)...). @@ -74,9 +94,10 @@ func (e *emitter) emitVectorSetOp(s *chplan.VectorSetOp) error { return nil case chplan.VectorSetUnless: // SELECT MetricName, Attributes, TimeUnix, Value - // FROM (SELECT * FROM () WHERE NOT IN (SELECT DISTINCT FROM ())) + // FROM (SELECT MetricName, Attributes, TimeUnix, Value FROM () WHERE NOT IN (SELECT DISTINCT FROM ())) inner := NewQuery(). - From(leftFrag). + Select(vectorSetOpOutputCols(s)...). + From(leftArm). Where(setOpInSubqueryFrag(s.Match, s.AttributesColumn, rightFrag, false /*notIn*/)) outer := NewQuery(). Select(vectorSetOpOutputCols(s)...). @@ -85,9 +106,9 @@ func (e *emitter) emitVectorSetOp(s *chplan.VectorSetOp) error { return nil case chplan.VectorSetOr: // SELECT MetricName, Attributes, TimeUnix, Value FROM ( - // (SELECT * FROM ()) + // (SELECT MetricName, Attributes, TimeUnix, Value FROM ()) // UNION ALL - // (SELECT * FROM () + // (SELECT MetricName, Attributes, TimeUnix, Value FROM () // WHERE NOT IN (SELECT DISTINCT FROM ())) // ) // @@ -98,9 +119,18 @@ func (e *emitter) emitVectorSetOp(s *chplan.VectorSetOp) error { // (and CH then asks for a supertype between the outer SELECT's // projection and the right UNION arm — Map vs. String — and // fails with NO_COMMON_TYPE). - leftSelect := NewQuery().From(leftFrag) + // + // Each arm projects the canonical 4-column shape via + // vectorSetOpCanonicalArmFrag so positional column unification + // across arms never hits the String-vs-Map supertype error even + // when one arm is a derived-shape RangeWindow / Aggregate that + // drops `__name__`. + leftSelect := NewQuery(). + Select(vectorSetOpOutputCols(s)...). + From(leftArm) rightSelect := NewQuery(). - From(rightFrag). + Select(vectorSetOpOutputCols(s)...). + From(rightArm). Where(setOpInSubqueryFrag(s.Match, s.AttributesColumn, leftFrag, false /*notIn*/)) outer := NewQuery(). Select(vectorSetOpOutputCols(s)...). @@ -111,6 +141,112 @@ func (e *emitter) emitVectorSetOp(s *chplan.VectorSetOp) error { return fmt.Errorf("%w: vector set op %q", ErrUnsupported, s.Op) } +// vectorSetOpCanonicalArmFrag returns a Frag rendering the per-arm +// canonical 4-column projection used inside the VectorSetOp UNION ALL / +// IN-subquery shapes. The arm's chplan node is inspected to decide +// whether `MetricName` is available as a real column from the rendered +// subquery: canonical-shape inputs (Scan / Filter(Scan) / a Project that +// names all four canonical columns) pass MetricName through by name; +// derived-shape inputs (RangeWindow / Aggregate / MetricsAggregate / +// MetricsHistogramOverTime / a Project on top of one of those) lack +// MetricName in their output schema and synthesise it as the empty +// string — mirroring `wrapWithSampleProjection`'s derived-shape branch. +// +// The Attributes / TimeUnix / Value columns are always referenced by +// name. Matrix-mode RangeWindow projects `anchor_ts AS TimeUnix` at +// emit time (see emitWindowedArrayMatrix), so TimeUnix resolves under +// the canonical alias in both instant and matrix shapes. +func vectorSetOpCanonicalArmFrag(s *chplan.VectorSetOp, arm chplan.Node, armFrag Frag) Frag { + var metricNameFrag Frag + if vectorSetOpArmIsDerivedShape(arm, s) { + metricNameFrag = As(Lit(""), s.MetricNameColumn) + } else { + metricNameFrag = Col(s.MetricNameColumn) + } + inner := NewQuery(). + Select( + metricNameFrag, + Col(s.AttributesColumn), + Col(s.TimestampColumn), + Col(s.ValueColumn), + ). + From(armFrag) + return inner.Frag() +} + +// vectorSetOpArmIsDerivedShape reports whether a VectorSetOp arm's +// chplan output schema lacks the canonical MetricName column. Mirrors +// `internal/api/prom/handler.go::isDerivedShape` but lives in the +// chsql package so the emitter can decide per-arm without taking a +// dependency on the HTTP-layer helper. The two functions must stay in +// sync; both treat RangeWindow / Aggregate / MetricsAggregate / +// MetricsHistogramOverTime — and a Project that does NOT expose all +// four canonical columns above one of those — as derived. +// +// Nested VectorSetOp arms are canonical: the recursive emit wraps each +// inner VectorSetOp in its own canonical-column SELECT, so a parent +// arm can reference MetricName by name. +func vectorSetOpArmIsDerivedShape(n chplan.Node, s *chplan.VectorSetOp) bool { + switch v := n.(type) { + case *chplan.RangeWindow, + *chplan.Aggregate, + *chplan.MetricsAggregate, + *chplan.MetricsHistogramOverTime: + return true + case *chplan.Filter: + return vectorSetOpArmIsDerivedShape(v.Input, s) + case *chplan.Project: + if vectorSetOpProjectExposesCanonical(v, s) { + return false + } + return vectorSetOpArmIsDerivedShape(v.Input, s) + } + return false +} + +// vectorSetOpProjectExposesCanonical reports whether p's projections +// name all four canonical Sample column outputs (MetricName / +// Attributes / TimeUnix / Value). Mirrors +// `internal/api/prom/handler.go::projectionExposesCanonical`; see that +// docstring for the full canonical-shape definition. An output is +// "named" when either Projection.Alias matches, or the Projection.Expr +// is a bare ColumnRef to the canonical column name with no Alias +// rewrite. +func vectorSetOpProjectExposesCanonical(p *chplan.Project, s *chplan.VectorSetOp) bool { + needed := map[string]bool{ + s.MetricNameColumn: false, + s.AttributesColumn: false, + s.TimestampColumn: false, + s.ValueColumn: false, + } + for _, proj := range p.Projections { + name := vectorSetOpProjectionOutputName(proj) + if _, ok := needed[name]; ok { + needed[name] = true + } + } + for _, ok := range needed { + if !ok { + return false + } + } + return true +} + +// vectorSetOpProjectionOutputName returns the column name a Projection +// exposes: the explicit Alias when set, otherwise the bare-ColumnRef +// name when the Expr is a column reference. Mirrors +// `internal/api/prom/handler.go::projectionOutputName`. +func vectorSetOpProjectionOutputName(p chplan.Projection) string { + if p.Alias != "" { + return p.Alias + } + if cr, ok := p.Expr.(*chplan.ColumnRef); ok { + return cr.Name + } + return "" +} + // vectorSetOpOutputCols returns the explicit projection list a vector // set op uses for its outer SELECT. Rendering MetricName / Attributes / // TimeUnix / Value explicitly (vs. the implicit `SELECT *`) lets the diff --git a/test/spec/promql/binary_and.txtar b/test/spec/promql/binary_and.txtar index c45dd41d..ffa8aaa0 100644 --- a/test/spec/promql/binary_and.txtar +++ b/test/spec/promql/binary_and.txtar @@ -39,4 +39,4 @@ VectorSetOp op=and match=default Filter predicate=(((MetricName = "http_server_request_b_total") AND (TimeUnix <= toDateTime64("2026-01-01 00:00:01.000000000", 9))) AND (TimeUnix > (toDateTime64("2026-01-01 00:00:01.000000000", 9) - toIntervalNanosecond(300000000000)))) Scan(otel_metrics_sum) -- sql -- -SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)) WHERE `Attributes` IN ((SELECT DISTINCT `Attributes` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) +SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) WHERE `Attributes` IN ((SELECT DISTINCT `Attributes` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) diff --git a/test/spec/promql/binary_and_ignoring.txtar b/test/spec/promql/binary_and_ignoring.txtar index ad456b1e..c255b7dc 100644 --- a/test/spec/promql/binary_and_ignoring.txtar +++ b/test/spec/promql/binary_and_ignoring.txtar @@ -40,4 +40,4 @@ VectorSetOp op=and match=ignoring(instance) Filter predicate=(((MetricName = "http_server_request_b_total") AND (TimeUnix <= toDateTime64("2026-01-01 00:00:01.000000000", 9))) AND (TimeUnix > (toDateTime64("2026-01-01 00:00:01.000000000", 9) - toIntervalNanosecond(300000000000)))) Scan(otel_metrics_sum) -- sql -- -SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)) WHERE mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) IN ((SELECT DISTINCT mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) +SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) WHERE mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) IN ((SELECT DISTINCT mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) diff --git a/test/spec/promql/binary_and_on.txtar b/test/spec/promql/binary_and_on.txtar index 0d19b120..6a21547a 100644 --- a/test/spec/promql/binary_and_on.txtar +++ b/test/spec/promql/binary_and_on.txtar @@ -41,4 +41,4 @@ VectorSetOp op=and match=on(job) Filter predicate=(((MetricName = "http_server_request_b_total") AND (TimeUnix <= toDateTime64("2026-01-01 00:00:01.000000000", 9))) AND (TimeUnix > (toDateTime64("2026-01-01 00:00:01.000000000", 9) - toIntervalNanosecond(300000000000)))) Scan(otel_metrics_sum) -- sql -- -SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)) WHERE mapFilter((k, v) -> k IN (?), `Attributes`) IN ((SELECT DISTINCT mapFilter((k, v) -> k IN (?), `Attributes`) FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) +SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) WHERE mapFilter((k, v) -> k IN (?), `Attributes`) IN ((SELECT DISTINCT mapFilter((k, v) -> k IN (?), `Attributes`) FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) diff --git a/test/spec/promql/binary_or.txtar b/test/spec/promql/binary_or.txtar index 5f05898e..4cf60fec 100644 --- a/test/spec/promql/binary_or.txtar +++ b/test/spec/promql/binary_or.txtar @@ -48,4 +48,4 @@ VectorSetOp op=or match=default Filter predicate=(((MetricName = "http_server_request_b_total") AND (TimeUnix <= toDateTime64("2026-01-01 00:00:01.000000000", 9))) AND (TimeUnix > (toDateTime64("2026-01-01 00:00:01.000000000", 9) - toIntervalNanosecond(300000000000)))) Scan(otel_metrics_sum) -- sql -- -SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM ((SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) UNION ALL (SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)) WHERE `Attributes` NOT IN ((SELECT DISTINCT `Attributes` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)))))) +SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM ((SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)))) UNION ALL (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) WHERE `Attributes` NOT IN ((SELECT DISTINCT `Attributes` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)))))) diff --git a/test/spec/promql/binary_or_ignoring.txtar b/test/spec/promql/binary_or_ignoring.txtar index 649d212a..31d0d6c0 100644 --- a/test/spec/promql/binary_or_ignoring.txtar +++ b/test/spec/promql/binary_or_ignoring.txtar @@ -48,4 +48,4 @@ VectorSetOp op=or match=ignoring(instance) Filter predicate=(((MetricName = "http_server_request_b_total") AND (TimeUnix <= toDateTime64("2026-01-01 00:00:01.000000000", 9))) AND (TimeUnix > (toDateTime64("2026-01-01 00:00:01.000000000", 9) - toIntervalNanosecond(300000000000)))) Scan(otel_metrics_sum) -- sql -- -SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM ((SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) UNION ALL (SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)) WHERE mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) NOT IN ((SELECT DISTINCT mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)))))) +SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM ((SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)))) UNION ALL (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) WHERE mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) NOT IN ((SELECT DISTINCT mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)))))) diff --git a/test/spec/promql/binary_or_increase_range_canonicalises_arms.txtar b/test/spec/promql/binary_or_increase_range_canonicalises_arms.txtar new file mode 100644 index 00000000..dd22a745 --- /dev/null +++ b/test/spec/promql/binary_or_increase_range_canonicalises_arms.txtar @@ -0,0 +1,90 @@ +# Regression: a PromQL `or` chain over `increase(metric[range])` arms in +# range mode (Step > 0) used to fail at ClickHouse with +# +# code: 386, message: There is no supertype for types String, +# Map(LowCardinality(String), String) because some of them are Maps +# and some of them are not +# +# — surfaced by PR #701's new `otelcol-observability` dashboard +# (`sum(increase(refused_log_records[5m]) or increase(refused_metric_points[5m]) or increase(refused_spans[5m]))`). +# +# Root cause: `A or B or C` parses as `(A or B) or C`. The inner `(A or B)` +# VectorSetOr arm projected the canonical 4 columns explicitly +# (`MetricName, Attributes, TimeUnix, Value`), while the outer right arm +# `C` was a matrix-shape RangeWindow whose `SELECT *` exposed +# `Attributes, anchor_ts, TimeUnix, Value` (no MetricName, because +# `increase` drops `__name__`). The UNION ALL between the two arms then +# tried to unify column position 0 — String (the inner arm's MetricName +# projection) against Map(LowCardinality(String), String) (the matrix +# RangeWindow's leading Attributes column) — and CH refused with +# NO_COMMON_TYPE. +# +# Fix: every VectorSetOp arm projects the canonical 4-column shape +# explicitly (synthesising `'' AS MetricName` for derived-shape arms +# like RangeWindow / Aggregate / MetricsAggregate), so positional column +# unification across the UNION arms always sees matching column types. +# This fixture pins the matrix-RangeWindow vs canonical-shape mismatch +# the dashboard sweep regressed; the existing `binary_or.txtar` covers +# the all-canonical-shape arm case. + +-- query.promql -- +sum(increase(otelcol_processor_refused_log_records[5m]) or increase(otelcol_processor_refused_metric_points[5m]) or increase(otelcol_processor_refused_spans[5m])) +-- range_step -- +1m +-- seed -- +CREATE TABLE otel_metrics_gauge ( + MetricName String, + Attributes Map(String, String), + TimeUnix DateTime64(9), + Value Float64 +) ENGINE = MergeTree ORDER BY (MetricName, Attributes, TimeUnix); +INSERT INTO otel_metrics_gauge VALUES + ('otelcol_processor_refused_log_records', map('processor', 'batch'), toDateTime64('2026-01-01 00:00:00', 9), 0.0), + ('otelcol_processor_refused_log_records', map('processor', 'batch'), toDateTime64('2026-01-01 00:01:00', 9), 5.0), + ('otelcol_processor_refused_log_records', map('processor', 'batch'), toDateTime64('2026-01-01 00:02:00', 9), 10.0), + ('otelcol_processor_refused_log_records', map('processor', 'batch'), toDateTime64('2026-01-01 00:03:00', 9), 15.0), + ('otelcol_processor_refused_log_records', map('processor', 'batch'), toDateTime64('2026-01-01 00:04:00', 9), 20.0), + ('otelcol_processor_refused_log_records', map('processor', 'batch'), toDateTime64('2026-01-01 00:05:00', 9), 25.0), + ('otelcol_processor_refused_metric_points', map('processor', 'batch'), toDateTime64('2026-01-01 00:00:00', 9), 0.0), + ('otelcol_processor_refused_metric_points', map('processor', 'batch'), toDateTime64('2026-01-01 00:01:00', 9), 2.0), + ('otelcol_processor_refused_metric_points', map('processor', 'batch'), toDateTime64('2026-01-01 00:02:00', 9), 4.0), + ('otelcol_processor_refused_metric_points', map('processor', 'batch'), toDateTime64('2026-01-01 00:03:00', 9), 6.0), + ('otelcol_processor_refused_metric_points', map('processor', 'batch'), toDateTime64('2026-01-01 00:04:00', 9), 8.0), + ('otelcol_processor_refused_metric_points', map('processor', 'batch'), toDateTime64('2026-01-01 00:05:00', 9), 10.0), + ('otelcol_processor_refused_spans', map('processor', 'batch'), toDateTime64('2026-01-01 00:00:00', 9), 0.0), + ('otelcol_processor_refused_spans', map('processor', 'batch'), toDateTime64('2026-01-01 00:01:00', 9), 1.0), + ('otelcol_processor_refused_spans', map('processor', 'batch'), toDateTime64('2026-01-01 00:02:00', 9), 2.0), + ('otelcol_processor_refused_spans', map('processor', 'batch'), toDateTime64('2026-01-01 00:03:00', 9), 3.0), + ('otelcol_processor_refused_spans', map('processor', 'batch'), toDateTime64('2026-01-01 00:04:00', 9), 4.0), + ('otelcol_processor_refused_spans', map('processor', 'batch'), toDateTime64('2026-01-01 00:05:00', 9), 5.0); +-- args -- +[0] string = "" +[1] string = "Map(String,String)" +[2] string = "" +[3] string = "otelcol_processor_refused_log_records" +[4] string = "" +[5] string = "otelcol_processor_refused_metric_points" +[6] string = "otelcol_processor_refused_log_records" +[7] string = "" +[8] string = "otelcol_processor_refused_spans" +[9] string = "" +[10] string = "otelcol_processor_refused_log_records" +[11] string = "" +[12] string = "otelcol_processor_refused_metric_points" +[13] string = "otelcol_processor_refused_log_records" +-- chplan -- +Project ["" AS MetricName, CAST(map(), "Map(String,String)") AS Attributes, bucket_ts AS TimeUnix, Value AS Value] + Aggregate groupBy=[TimeUnix AS bucket_ts] funcs=[sum(Value) AS Value] + VectorSetOp op=or match=default + VectorSetOp op=or match=default + RangeWindow func=increase range=5m0s step=1m0s outerRange=5m0s ts=TimeUnix value=Value groupBy=[Attributes] start=2026-01-01T00:00:00Z end=2026-01-01T00:05:00Z + Filter predicate=(MetricName = "otelcol_processor_refused_log_records") + Scan(otel_metrics_gauge) + RangeWindow func=increase range=5m0s step=1m0s outerRange=5m0s ts=TimeUnix value=Value groupBy=[Attributes] start=2026-01-01T00:00:00Z end=2026-01-01T00:05:00Z + Filter predicate=(MetricName = "otelcol_processor_refused_metric_points") + Scan(otel_metrics_gauge) + RangeWindow func=increase range=5m0s step=1m0s outerRange=5m0s ts=TimeUnix value=Value groupBy=[Attributes] start=2026-01-01T00:00:00Z end=2026-01-01T00:05:00Z + Filter predicate=(MetricName = "otelcol_processor_refused_spans") + Scan(otel_metrics_gauge) +-- sql -- +SELECT ? AS `MetricName`, CAST(map(), ?) AS `Attributes`, `bucket_ts` AS `TimeUnix`, `Value` AS `Value` FROM (SELECT `TimeUnix` AS `bucket_ts`, sum(`Value`) AS `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM ((SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM ((SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT ? AS `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `Attributes`, `anchor_ts`, anchor_ts AS `TimeUnix`, if(sampled_interval > 0, counter_delta * (sampled_interval + if(counter_delta > 0 AND first_val >= 0, least(duration_to_start, sampled_interval * first_val / counter_delta), duration_to_start) + duration_to_end) / sampled_interval, nan) AS `Value` FROM (SELECT `Attributes`, `anchor_ts`, `window_vals`, `counter_delta`, `first_val`, toFloat64(dateDiff('nanosecond', first_ts, last_ts)) / 1e9 AS `sampled_interval`, if(toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9) AS `duration_to_start`, if(toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9) AS `duration_to_end` FROM (SELECT `Attributes`, `anchor_ts`, arrayMap(p -> tupleElement(p, 2), window_pairs) AS `window_vals`, arraySum(arrayMap((p, c) -> if(c < p, c, c - p), arrayPopBack(arrayMap(x -> tupleElement(x, 2), window_pairs)), arrayPopFront(arrayMap(x -> tupleElement(x, 2), window_pairs)))) AS `counter_delta`, tupleElement(window_pairs[1], 1) AS `first_ts`, tupleElement(window_pairs[length(window_pairs)], 1) AS `last_ts`, tupleElement(window_pairs[1], 2) AS `first_val` FROM (SELECT `Attributes`, `anchor_ts`, arrayFilter(p -> tupleElement(p, 1) > anchor_ts - toIntervalNanosecond(300000000000) AND tupleElement(p, 1) <= anchor_ts, series_array) AS `window_pairs` FROM (SELECT `Attributes`, `series_array`, arrayJoin(arrayMap(i -> toDateTime64('2026-01-01 00:05:00.000000000', 9) - toIntervalNanosecond(i * 60000000000), range(0, 6))) AS `anchor_ts` FROM (SELECT `Attributes`, arraySort(groupArray((`TimeUnix`, `Value`))) AS `series_array` FROM (SELECT * FROM `otel_metrics_gauge` WHERE (`MetricName` = ?)) GROUP BY `Attributes`))))) WHERE length(`window_vals`) >= 2))) UNION ALL (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT ? AS `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `Attributes`, `anchor_ts`, anchor_ts AS `TimeUnix`, if(sampled_interval > 0, counter_delta * (sampled_interval + if(counter_delta > 0 AND first_val >= 0, least(duration_to_start, sampled_interval * first_val / counter_delta), duration_to_start) + duration_to_end) / sampled_interval, nan) AS `Value` FROM (SELECT `Attributes`, `anchor_ts`, `window_vals`, `counter_delta`, `first_val`, toFloat64(dateDiff('nanosecond', first_ts, last_ts)) / 1e9 AS `sampled_interval`, if(toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9) AS `duration_to_start`, if(toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9) AS `duration_to_end` FROM (SELECT `Attributes`, `anchor_ts`, arrayMap(p -> tupleElement(p, 2), window_pairs) AS `window_vals`, arraySum(arrayMap((p, c) -> if(c < p, c, c - p), arrayPopBack(arrayMap(x -> tupleElement(x, 2), window_pairs)), arrayPopFront(arrayMap(x -> tupleElement(x, 2), window_pairs)))) AS `counter_delta`, tupleElement(window_pairs[1], 1) AS `first_ts`, tupleElement(window_pairs[length(window_pairs)], 1) AS `last_ts`, tupleElement(window_pairs[1], 2) AS `first_val` FROM (SELECT `Attributes`, `anchor_ts`, arrayFilter(p -> tupleElement(p, 1) > anchor_ts - toIntervalNanosecond(300000000000) AND tupleElement(p, 1) <= anchor_ts, series_array) AS `window_pairs` FROM (SELECT `Attributes`, `series_array`, arrayJoin(arrayMap(i -> toDateTime64('2026-01-01 00:05:00.000000000', 9) - toIntervalNanosecond(i * 60000000000), range(0, 6))) AS `anchor_ts` FROM (SELECT `Attributes`, arraySort(groupArray((`TimeUnix`, `Value`))) AS `series_array` FROM (SELECT * FROM `otel_metrics_gauge` WHERE (`MetricName` = ?)) GROUP BY `Attributes`))))) WHERE length(`window_vals`) >= 2)) WHERE `Attributes` NOT IN ((SELECT DISTINCT `Attributes` FROM (SELECT `Attributes`, `anchor_ts`, anchor_ts AS `TimeUnix`, if(sampled_interval > 0, counter_delta * (sampled_interval + if(counter_delta > 0 AND first_val >= 0, least(duration_to_start, sampled_interval * first_val / counter_delta), duration_to_start) + duration_to_end) / sampled_interval, nan) AS `Value` FROM (SELECT `Attributes`, `anchor_ts`, `window_vals`, `counter_delta`, `first_val`, toFloat64(dateDiff('nanosecond', first_ts, last_ts)) / 1e9 AS `sampled_interval`, if(toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9) AS `duration_to_start`, if(toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9) AS `duration_to_end` FROM (SELECT `Attributes`, `anchor_ts`, arrayMap(p -> tupleElement(p, 2), window_pairs) AS `window_vals`, arraySum(arrayMap((p, c) -> if(c < p, c, c - p), arrayPopBack(arrayMap(x -> tupleElement(x, 2), window_pairs)), arrayPopFront(arrayMap(x -> tupleElement(x, 2), window_pairs)))) AS `counter_delta`, tupleElement(window_pairs[1], 1) AS `first_ts`, tupleElement(window_pairs[length(window_pairs)], 1) AS `last_ts`, tupleElement(window_pairs[1], 2) AS `first_val` FROM (SELECT `Attributes`, `anchor_ts`, arrayFilter(p -> tupleElement(p, 1) > anchor_ts - toIntervalNanosecond(300000000000) AND tupleElement(p, 1) <= anchor_ts, series_array) AS `window_pairs` FROM (SELECT `Attributes`, `series_array`, arrayJoin(arrayMap(i -> toDateTime64('2026-01-01 00:05:00.000000000', 9) - toIntervalNanosecond(i * 60000000000), range(0, 6))) AS `anchor_ts` FROM (SELECT `Attributes`, arraySort(groupArray((`TimeUnix`, `Value`))) AS `series_array` FROM (SELECT * FROM `otel_metrics_gauge` WHERE (`MetricName` = ?)) GROUP BY `Attributes`))))) WHERE length(`window_vals`) >= 2)))))))) UNION ALL (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT ? AS `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `Attributes`, `anchor_ts`, anchor_ts AS `TimeUnix`, if(sampled_interval > 0, counter_delta * (sampled_interval + if(counter_delta > 0 AND first_val >= 0, least(duration_to_start, sampled_interval * first_val / counter_delta), duration_to_start) + duration_to_end) / sampled_interval, nan) AS `Value` FROM (SELECT `Attributes`, `anchor_ts`, `window_vals`, `counter_delta`, `first_val`, toFloat64(dateDiff('nanosecond', first_ts, last_ts)) / 1e9 AS `sampled_interval`, if(toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9) AS `duration_to_start`, if(toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9) AS `duration_to_end` FROM (SELECT `Attributes`, `anchor_ts`, arrayMap(p -> tupleElement(p, 2), window_pairs) AS `window_vals`, arraySum(arrayMap((p, c) -> if(c < p, c, c - p), arrayPopBack(arrayMap(x -> tupleElement(x, 2), window_pairs)), arrayPopFront(arrayMap(x -> tupleElement(x, 2), window_pairs)))) AS `counter_delta`, tupleElement(window_pairs[1], 1) AS `first_ts`, tupleElement(window_pairs[length(window_pairs)], 1) AS `last_ts`, tupleElement(window_pairs[1], 2) AS `first_val` FROM (SELECT `Attributes`, `anchor_ts`, arrayFilter(p -> tupleElement(p, 1) > anchor_ts - toIntervalNanosecond(300000000000) AND tupleElement(p, 1) <= anchor_ts, series_array) AS `window_pairs` FROM (SELECT `Attributes`, `series_array`, arrayJoin(arrayMap(i -> toDateTime64('2026-01-01 00:05:00.000000000', 9) - toIntervalNanosecond(i * 60000000000), range(0, 6))) AS `anchor_ts` FROM (SELECT `Attributes`, arraySort(groupArray((`TimeUnix`, `Value`))) AS `series_array` FROM (SELECT * FROM `otel_metrics_gauge` WHERE (`MetricName` = ?)) GROUP BY `Attributes`))))) WHERE length(`window_vals`) >= 2)) WHERE `Attributes` NOT IN ((SELECT DISTINCT `Attributes` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM ((SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT ? AS `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `Attributes`, `anchor_ts`, anchor_ts AS `TimeUnix`, if(sampled_interval > 0, counter_delta * (sampled_interval + if(counter_delta > 0 AND first_val >= 0, least(duration_to_start, sampled_interval * first_val / counter_delta), duration_to_start) + duration_to_end) / sampled_interval, nan) AS `Value` FROM (SELECT `Attributes`, `anchor_ts`, `window_vals`, `counter_delta`, `first_val`, toFloat64(dateDiff('nanosecond', first_ts, last_ts)) / 1e9 AS `sampled_interval`, if(toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9) AS `duration_to_start`, if(toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9) AS `duration_to_end` FROM (SELECT `Attributes`, `anchor_ts`, arrayMap(p -> tupleElement(p, 2), window_pairs) AS `window_vals`, arraySum(arrayMap((p, c) -> if(c < p, c, c - p), arrayPopBack(arrayMap(x -> tupleElement(x, 2), window_pairs)), arrayPopFront(arrayMap(x -> tupleElement(x, 2), window_pairs)))) AS `counter_delta`, tupleElement(window_pairs[1], 1) AS `first_ts`, tupleElement(window_pairs[length(window_pairs)], 1) AS `last_ts`, tupleElement(window_pairs[1], 2) AS `first_val` FROM (SELECT `Attributes`, `anchor_ts`, arrayFilter(p -> tupleElement(p, 1) > anchor_ts - toIntervalNanosecond(300000000000) AND tupleElement(p, 1) <= anchor_ts, series_array) AS `window_pairs` FROM (SELECT `Attributes`, `series_array`, arrayJoin(arrayMap(i -> toDateTime64('2026-01-01 00:05:00.000000000', 9) - toIntervalNanosecond(i * 60000000000), range(0, 6))) AS `anchor_ts` FROM (SELECT `Attributes`, arraySort(groupArray((`TimeUnix`, `Value`))) AS `series_array` FROM (SELECT * FROM `otel_metrics_gauge` WHERE (`MetricName` = ?)) GROUP BY `Attributes`))))) WHERE length(`window_vals`) >= 2))) UNION ALL (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT ? AS `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `Attributes`, `anchor_ts`, anchor_ts AS `TimeUnix`, if(sampled_interval > 0, counter_delta * (sampled_interval + if(counter_delta > 0 AND first_val >= 0, least(duration_to_start, sampled_interval * first_val / counter_delta), duration_to_start) + duration_to_end) / sampled_interval, nan) AS `Value` FROM (SELECT `Attributes`, `anchor_ts`, `window_vals`, `counter_delta`, `first_val`, toFloat64(dateDiff('nanosecond', first_ts, last_ts)) / 1e9 AS `sampled_interval`, if(toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9) AS `duration_to_start`, if(toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9) AS `duration_to_end` FROM (SELECT `Attributes`, `anchor_ts`, arrayMap(p -> tupleElement(p, 2), window_pairs) AS `window_vals`, arraySum(arrayMap((p, c) -> if(c < p, c, c - p), arrayPopBack(arrayMap(x -> tupleElement(x, 2), window_pairs)), arrayPopFront(arrayMap(x -> tupleElement(x, 2), window_pairs)))) AS `counter_delta`, tupleElement(window_pairs[1], 1) AS `first_ts`, tupleElement(window_pairs[length(window_pairs)], 1) AS `last_ts`, tupleElement(window_pairs[1], 2) AS `first_val` FROM (SELECT `Attributes`, `anchor_ts`, arrayFilter(p -> tupleElement(p, 1) > anchor_ts - toIntervalNanosecond(300000000000) AND tupleElement(p, 1) <= anchor_ts, series_array) AS `window_pairs` FROM (SELECT `Attributes`, `series_array`, arrayJoin(arrayMap(i -> toDateTime64('2026-01-01 00:05:00.000000000', 9) - toIntervalNanosecond(i * 60000000000), range(0, 6))) AS `anchor_ts` FROM (SELECT `Attributes`, arraySort(groupArray((`TimeUnix`, `Value`))) AS `series_array` FROM (SELECT * FROM `otel_metrics_gauge` WHERE (`MetricName` = ?)) GROUP BY `Attributes`))))) WHERE length(`window_vals`) >= 2)) WHERE `Attributes` NOT IN ((SELECT DISTINCT `Attributes` FROM (SELECT `Attributes`, `anchor_ts`, anchor_ts AS `TimeUnix`, if(sampled_interval > 0, counter_delta * (sampled_interval + if(counter_delta > 0 AND first_val >= 0, least(duration_to_start, sampled_interval * first_val / counter_delta), duration_to_start) + duration_to_end) / sampled_interval, nan) AS `Value` FROM (SELECT `Attributes`, `anchor_ts`, `window_vals`, `counter_delta`, `first_val`, toFloat64(dateDiff('nanosecond', first_ts, last_ts)) / 1e9 AS `sampled_interval`, if(toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', anchor_ts - toIntervalNanosecond(300000000000), first_ts)) / 1e9) AS `duration_to_start`, if(toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9 >= 1.1 * sampled_interval / (length(window_vals) - 1), sampled_interval / (length(window_vals) - 1) / 2, toFloat64(dateDiff('nanosecond', last_ts, anchor_ts)) / 1e9) AS `duration_to_end` FROM (SELECT `Attributes`, `anchor_ts`, arrayMap(p -> tupleElement(p, 2), window_pairs) AS `window_vals`, arraySum(arrayMap((p, c) -> if(c < p, c, c - p), arrayPopBack(arrayMap(x -> tupleElement(x, 2), window_pairs)), arrayPopFront(arrayMap(x -> tupleElement(x, 2), window_pairs)))) AS `counter_delta`, tupleElement(window_pairs[1], 1) AS `first_ts`, tupleElement(window_pairs[length(window_pairs)], 1) AS `last_ts`, tupleElement(window_pairs[1], 2) AS `first_val` FROM (SELECT `Attributes`, `anchor_ts`, arrayFilter(p -> tupleElement(p, 1) > anchor_ts - toIntervalNanosecond(300000000000) AND tupleElement(p, 1) <= anchor_ts, series_array) AS `window_pairs` FROM (SELECT `Attributes`, `series_array`, arrayJoin(arrayMap(i -> toDateTime64('2026-01-01 00:05:00.000000000', 9) - toIntervalNanosecond(i * 60000000000), range(0, 6))) AS `anchor_ts` FROM (SELECT `Attributes`, arraySort(groupArray((`TimeUnix`, `Value`))) AS `series_array` FROM (SELECT * FROM `otel_metrics_gauge` WHERE (`MetricName` = ?)) GROUP BY `Attributes`))))) WHERE length(`window_vals`) >= 2))))))))))) GROUP BY `TimeUnix`) diff --git a/test/spec/promql/binary_unless.txtar b/test/spec/promql/binary_unless.txtar index 079df5d0..cb869730 100644 --- a/test/spec/promql/binary_unless.txtar +++ b/test/spec/promql/binary_unless.txtar @@ -39,4 +39,4 @@ VectorSetOp op=unless match=default Filter predicate=(((MetricName = "http_server_request_b_total") AND (TimeUnix <= toDateTime64("2026-01-01 00:00:01.000000000", 9))) AND (TimeUnix > (toDateTime64("2026-01-01 00:00:01.000000000", 9) - toIntervalNanosecond(300000000000)))) Scan(otel_metrics_sum) -- sql -- -SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)) WHERE `Attributes` NOT IN ((SELECT DISTINCT `Attributes` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) +SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) WHERE `Attributes` NOT IN ((SELECT DISTINCT `Attributes` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) diff --git a/test/spec/promql/binary_unless_ignoring.txtar b/test/spec/promql/binary_unless_ignoring.txtar index 4de3392d..682da891 100644 --- a/test/spec/promql/binary_unless_ignoring.txtar +++ b/test/spec/promql/binary_unless_ignoring.txtar @@ -41,4 +41,4 @@ VectorSetOp op=unless match=ignoring(instance) Filter predicate=(((MetricName = "http_server_request_b_total") AND (TimeUnix <= toDateTime64("2026-01-01 00:00:01.000000000", 9))) AND (TimeUnix > (toDateTime64("2026-01-01 00:00:01.000000000", 9) - toIntervalNanosecond(300000000000)))) Scan(otel_metrics_sum) -- sql -- -SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT * FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)) WHERE mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) NOT IN ((SELECT DISTINCT mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))))) +SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName`, `Attributes`, `TimeUnix`, `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`))) WHERE mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) NOT IN ((SELECT DISTINCT mapFilter((k, v) -> NOT (k IN (?)), `Attributes`) FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, `lwr_ts` AS `TimeUnix`, `lwr_value` AS `Value` FROM (SELECT `MetricName` AS `MetricName`, `Attributes` AS `Attributes`, max(`TimeUnix`) AS `lwr_ts`, argMax(`Value`, `TimeUnix`) AS `lwr_value` FROM (SELECT * FROM `otel_metrics_sum` PREWHERE (`MetricName` = ?) WHERE (`TimeUnix` <= toDateTime64(?, ?)) AND (`TimeUnix` > (toDateTime64(?, ?) - toIntervalNanosecond(?)))) GROUP BY `MetricName`, `Attributes`)))))