diff --git a/promql/engine.go b/promql/engine.go index 07fb03d66c..5a08da121c 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -2191,8 +2191,8 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value, mat[i].Histograms[j].H = mat[i].Histograms[j].H.Copy().Mul(-1) } } - if !ev.enableDelayedNameRemoval && mat.ContainsSameLabelset() { - ev.errorf("vector cannot contain metrics with the same labelset") + if !ev.enableDelayedNameRemoval { + mat = ev.mergeSeriesWithSameLabelset(mat) } } return mat, ws diff --git a/promql/engine_test.go b/promql/engine_test.go index 80bb75c945..208ac4f89d 100644 --- a/promql/engine_test.go +++ b/promql/engine_test.go @@ -3946,6 +3946,41 @@ eval instant at 1m histogram_fraction(-Inf, 0.7071067811865475, histogram_nan) {case="100% NaNs"} 0.0 {case="20% NaNs"} 0.4 +# Test unary negation with non-overlapping series that have different metric names. +# After negation, the __name__ label is dropped, so series with different names +# but same other labels should merge if they don't overlap in time. +clear +load 20m + http_requests{job="api"} 2 _ + http_errors{job="api"} _ 4 + +eval instant at 0 -{job="api"} + {job="api"} -2 + +eval instant at 20m -{job="api"} + {job="api"} -4 + +eval range from 0 to 20m step 20m -{job="api"} + {job="api"} -2 -4 + +# Test unary negation failure with overlapping timestamps (same labelset at same time). +clear +load 1m + http_requests{job="api"} 1 + http_errors{job="api"} 2 + +eval_fail instant at 0 -{job="api"} + +# Test unary negation with "or" operator combining metrics with removed names. +clear +load 10m + metric_a 1 _ + metric_b 3 4 + +# Use "-" unary operator as a simple way to remove the metric name. +eval range from 0 to 20m step 10m -metric_a or -metric_b + {} -1 -4 + `, engine) } diff --git a/promql/promqltest/testdata/operators.test b/promql/promqltest/testdata/operators.test index e570be9630..cd608b3c36 100644 --- a/promql/promqltest/testdata/operators.test +++ b/promql/promqltest/testdata/operators.test @@ -980,3 +980,40 @@ eval instant at 10m (testhistogram) and on() (vector(-1) == 1) eval range from 0 to 10m step 5m (testhistogram) and on() (vector(-1) == 1) clear + +# Test unary negation with non-overlapping series that have different metric names. +# After negation, the __name__ label is dropped, so series with different names +# but same other labels should merge if they don't overlap in time. +load 20m + http_requests{job="api"} 2 _ + http_errors{job="api"} _ 4 + +eval instant at 0 -{job="api"} + {job="api"} -2 + +eval instant at 20m -{job="api"} + {job="api"} -4 + +eval range from 0 to 20m step 20m -{job="api"} + {job="api"} -2 -4 + +# Test unary negation failure with overlapping timestamps (same labelset at same time). +clear +load 1m + http_requests{job="api"} 1 + http_errors{job="api"} 2 + +eval_fail instant at 0 -{job="api"} + +clear + +# Test unary negation with "or" operator combining metrics with removed names. +load 10m + metric_a 1 _ + metric_b 3 4 + +# Use "-" unary operator as a simple way to remove the metric name. +eval range from 0 to 20m step 10m -metric_a or -metric_b + {} -1 -4 + +clear