diff --git a/crates/frontend-promql/src/promql.rs b/crates/frontend-promql/src/promql.rs index 2d1cfa9f..bfea12b9 100644 --- a/crates/frontend-promql/src/promql.rs +++ b/crates/frontend-promql/src/promql.rs @@ -388,7 +388,14 @@ fn walk_binary(bin: &BinaryExpr) -> Result { let (kind, labels) = match &m.matching { Some(LabelModifier::Include(ls)) => (VectorMatchKind::On, ls.labels.clone()), Some(LabelModifier::Exclude(ls)) => (VectorMatchKind::Ignoring, ls.labels.clone()), - None => (VectorMatchKind::On, vec![]), + // No explicit `on(…)`/`ignoring(…)` — the parser attaches a default + // modifier to every set op (`and`/`or`/`unless`). The default is + // "match on all shared labels", which is exactly `ignoring([])` + // (ignore no labels). Representing it as `Ignoring([])` — not + // `On([])` — keeps it distinct from an explicit `on()` (match on the + // empty label set) while making it correctly equal to an explicit + // `ignoring()` (issue #68). + None => (VectorMatchKind::Ignoring, vec![]), }; let grouping = match &m.card { VectorMatchCardinality::ManyToOne(ls) => Some(VectorGrouping { diff --git a/crates/frontend-promql/tests/promql_equivalence.rs b/crates/frontend-promql/tests/promql_equivalence.rs index b097e084..1dc2255f 100644 --- a/crates/frontend-promql/tests/promql_equivalence.rs +++ b/crates/frontend-promql/tests/promql_equivalence.rs @@ -141,6 +141,20 @@ fn rate_and_irate_share_the_same_intent() { assert_equiv(&["rate(m[5m])", "irate(m[5m])"]); } +#[test] +fn set_op_default_match_is_ignoring_empty_not_on_empty() { + // Issue #68. A set op's *default* matching ("match on all shared labels") is + // `ignoring([])`, NOT `on([])`. So: + // - default `a and b` must stay DISTINCT from explicit `a and on() b` + // (which matches on the empty label set), and + // - default `a and b` must EQUAL explicit `a and ignoring() b` + // (ignore no labels ⇒ match on all shared labels). + assert_distinct("a and b", "a and on() b"); + assert_distinct("a or b", "a or on() b"); + assert_equiv(&["a and b", "a and ignoring() b"]); + assert_equiv(&["a unless b", "a unless ignoring() b"]); +} + // ───────────────────────────────────────────────────────────────────────────── // 4. Distinct semantics we cannot faithfully represent are REJECTED, not // silently merged into a wrong intent. (Each previously mislowered.)