diff --git a/promql/parser/printer.go b/promql/parser/printer.go index 2531bb6272..cc4f1202df 100644 --- a/promql/parser/printer.go +++ b/promql/parser/printer.go @@ -109,7 +109,7 @@ func writeLabels(b *bytes.Buffer, ss []string) { if i > 0 { b.WriteString(", ") } - if !model.LegacyValidation.IsValidMetricName(s) { + if !model.LegacyValidation.IsValidLabelName(s) { b.Write(strconv.AppendQuote(b.AvailableBuffer(), s)) } else { b.WriteString(s) @@ -147,6 +147,7 @@ func (node *BinaryExpr) ShortString() string { func (node *BinaryExpr) getMatchingStr() string { matching := "" + var b bytes.Buffer vm := node.VectorMatching if vm != nil { if len(vm.MatchingLabels) > 0 || vm.On || vm.Card == CardManyToOne || vm.Card == CardOneToMany { @@ -154,7 +155,10 @@ func (node *BinaryExpr) getMatchingStr() string { if vm.On { vmTag = "on" } - matching = fmt.Sprintf(" %s (%s)", vmTag, strings.Join(vm.MatchingLabels, ", ")) + b.WriteString(" " + vmTag + " (") + writeLabels(&b, vm.MatchingLabels) + b.WriteString(")") + matching = b.String() } if vm.Card == CardManyToOne || vm.Card == CardOneToMany { @@ -162,7 +166,11 @@ func (node *BinaryExpr) getMatchingStr() string { if vm.Card == CardManyToOne { vmCard = "left" } - matching += fmt.Sprintf(" group_%s (%s)", vmCard, strings.Join(vm.Include, ", ")) + b.Reset() + b.WriteString(" group_" + vmCard + " (") + writeLabels(&b, vm.Include) + b.WriteString(")") + matching += b.String() } } return matching diff --git a/promql/parser/printer_test.go b/promql/parser/printer_test.go index b7fa3e6ccb..bce4302d83 100644 --- a/promql/parser/printer_test.go +++ b/promql/parser/printer_test.go @@ -284,6 +284,18 @@ func TestExprString(t *testing.T) { { in: `predict_linear(foo[1h], 3000)`, }, + { + in: `sum by("üüü") (foo)`, + out: `sum by ("üüü") (foo)`, + }, + { + in: `sum without("äää") (foo)`, + out: `sum without ("äää") (foo)`, + }, + { + in: `count by("ööö", job) (foo)`, + out: `count by ("ööö", job) (foo)`, + }, } EnableExtendedRangeSelectors = true @@ -409,3 +421,55 @@ func TestVectorSelector_String(t *testing.T) { }) } } + +func TestBinaryExprUTF8Labels(t *testing.T) { + testCases := []struct { + name string + input string + expected string + }{ + { + name: "UTF-8 labels in on clause", + input: `foo / on("äää") bar`, + expected: `foo / on ("äää") bar`, + }, + { + name: "UTF-8 labels in ignoring clause", + input: `foo / ignoring("üüü") bar`, + expected: `foo / ignoring ("üüü") bar`, + }, + { + name: "UTF-8 labels in group_left clause", + input: `foo / on("äää") group_left("ööö") bar`, + expected: `foo / on ("äää") group_left ("ööö") bar`, + }, + { + name: "UTF-8 labels in group_right clause", + input: `foo / on("äää") group_right("ööö") bar`, + expected: `foo / on ("äää") group_right ("ööö") bar`, + }, + { + name: "Mixed legacy and UTF-8 labels", + input: `foo / on(legacy, "üüü") bar`, + expected: `foo / on (legacy, "üüü") bar`, + }, + { + name: "Legacy labels only (should not quote)", + input: `foo / on(job, instance) bar`, + expected: `foo / on (job, instance) bar`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + expr, err := ParseExpr(tc.input) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + result := expr.String() + if result != tc.expected { + t.Errorf("Expected: %s\nGot: %s", tc.expected, result) + } + }) + } +}