Skip to content

Commit cc85be9

Browse files
committed
Avoid unnecessary parentheses in simple binary expressions
Fixes #54 needBinaryOpArgParens now takes the parent operator as a parameter and uses three-tier logic: add parens when (1) the child op has lower priority than the parent, (2) the parent is a non-arithmetic op (or, and, comparisons, etc.), or (3) the child subtree contains non-trivial nodes (function calls, group/join modifiers, etc.). Plain arithmetic chains of numbers and metric names — 1 + 2 + 3, a + b + c — no longer get wrapped in redundant parentheses. Tests updated accordingly. w
1 parent 0dc0e2d commit cc85be9

File tree

5 files changed

+72
-21
lines changed

5 files changed

+72
-21
lines changed

optimizer_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestPushdownBinaryOpFilters(t *testing.T) {
5050
f(`foo * ignoring(x) bar`, `{a="b"}`, `foo{a="b"} * ignoring(x) bar{a="b"}`)
5151
f(`foo{f1!~"x"} UNLEss bar{f2=~"y.+"}`, `{a="b",x=~"y"}`, `foo{a="b",f1!~"x",x=~"y"} unless bar{a="b",f2=~"y.+",x=~"y"}`)
5252
f(`a / sum(x)`, `{a="b",c=~"foo|bar"}`, `a{a="b",c=~"foo|bar"} / sum(x)`)
53-
f(`round(rate(x[5m] offset -1h)) + 123 / {a="b"}`, `{x!="y"}`, `round(rate(x{x!="y"}[5m] offset -1h)) + (123 / {a="b",x!="y"})`)
53+
f(`round(rate(x[5m] offset -1h)) + 123 / {a="b"}`, `{x!="y"}`, `round(rate(x{x!="y"}[5m] offset -1h)) + 123 / {a="b",x!="y"}`)
5454
f(`scalar(foo)+bar`, `{a="b"}`, `scalar(foo) + bar{a="b"}`)
5555
f(`vector(foo)`, `{a="b"}`, `vector(foo{a="b"})`)
5656
f(`{a="b"} + on() group_left() {c="d"}`, `{a="b"}`, `{a="b"} + on() group_left() {c="d"}`)
@@ -382,9 +382,9 @@ func TestOptimize(t *testing.T) {
382382
f(`rate(sum(foo[5m:]) by (baz)) + bar{baz="a"}`, `rate(sum(foo{baz="a"}[5m:]) by(baz)) + bar{baz="a"}`)
383383

384384
// binary ops with constants or scalars
385-
f(`100 * foo / bar{baz="a"}`, `(100 * foo{baz="a"}) / bar{baz="a"}`)
386-
f(`foo * 100 / bar{baz="a"}`, `(foo{baz="a"} * 100) / bar{baz="a"}`)
387-
f(`foo / bar{baz="a"} * 100`, `(foo{baz="a"} / bar{baz="a"}) * 100`)
385+
f(`100 * foo / bar{baz="a"}`, `100 * foo{baz="a"} / bar{baz="a"}`)
386+
f(`foo * 100 / bar{baz="a"}`, `foo{baz="a"} * 100 / bar{baz="a"}`)
387+
f(`foo / bar{baz="a"} * 100`, `foo{baz="a"} / bar{baz="a"} * 100`)
388388
f(`scalar(x) * foo / bar{baz="a"}`, `(scalar(x) * foo{baz="a"}) / bar{baz="a"}`)
389389
f(`SCALAR(x) * foo / bar{baz="a"}`, `(SCALAR(x) * foo{baz="a"}) / bar{baz="a"}`)
390390
f(`100 * on(foo) bar{baz="z"} + a`, `(100 * on(foo) bar{baz="z"}) + a`)

parser.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,11 +1933,11 @@ func (be *BinaryOpExpr) appendStringNoKeepMetricNames(dst []byte) []byte {
19331933
}
19341934

19351935
func (be *BinaryOpExpr) needLeftParens() bool {
1936-
return needBinaryOpArgParens(be.Left)
1936+
return needBinaryOpArgParens(be.Left, be.Op)
19371937
}
19381938

19391939
func (be *BinaryOpExpr) needRightParens() bool {
1940-
if needBinaryOpArgParens(be.Right) {
1940+
if needBinaryOpArgParens(be.Right, be.Op) {
19411941
return true
19421942
}
19431943
switch t := be.Right.(type) {
@@ -1974,10 +1974,21 @@ func (be *BinaryOpExpr) appendModifiers(dst []byte) []byte {
19741974
return dst
19751975
}
19761976

1977-
func needBinaryOpArgParens(arg Expr) bool {
1977+
func needBinaryOpArgParens(arg Expr, parentOp string) bool {
19781978
switch t := arg.(type) {
19791979
case *BinaryOpExpr:
1980-
return true
1980+
// Parens are required when the child op has lower priority than the parent op,
1981+
// since removing them would change the evaluation order.
1982+
if binaryOpPriority(t.Op) < binaryOpPriority(parentOp) {
1983+
return true
1984+
}
1985+
1986+
if parentOp != "+" && parentOp != "-" && parentOp != "*" && parentOp != "/" {
1987+
return true
1988+
}
1989+
1990+
// Same op: parens are only needed when the sub-expression is not a simple leaf chain.
1991+
return !isBinaryOpLeafSimple(t)
19811992
case *RollupExpr:
19821993
if be, ok := t.Expr.(*BinaryOpExpr); ok && be.KeepMetricNames {
19831994
return true
@@ -1988,6 +1999,24 @@ func needBinaryOpArgParens(arg Expr) bool {
19881999
}
19892000
}
19902001

2002+
func isBinaryOpLeafSimple(arg Expr) bool {
2003+
switch t := arg.(type) {
2004+
case *NumberExpr:
2005+
return true
2006+
case *MetricExpr:
2007+
metricName := t.getMetricName()
2008+
return !isReservedBinaryOpIdent(metricName)
2009+
case *BinaryOpExpr:
2010+
if t.GroupModifier.Op != "" || t.KeepMetricNames || t.JoinModifier.Op != "" {
2011+
return false
2012+
}
2013+
2014+
return isBinaryOpLeafSimple(t.Left) && isBinaryOpLeafSimple(t.Right)
2015+
default:
2016+
return false
2017+
}
2018+
}
2019+
19912020
func isReservedBinaryOpIdent(s string) bool {
19922021
return isBinaryOpGroupModifier(s) || isBinaryOpJoinModifier(s) || isBinaryOpBoolModifier(s) || isPrefixModifier(s)
19932022
}

parser_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,8 @@ func TestParseSuccess(t *testing.T) {
397397
another(`group_left / (sum(1, 2))`, `group_left / sum(1, 2)`)
398398

399399
// parensExpr
400-
another(`(-foo + ((bar) / (baz))) + ((23))`, `((0 - foo) + (bar / baz)) + 23`)
401-
another(`(FOO + ((Bar) / (baZ))) + ((23))`, `(FOO + (Bar / baZ)) + 23`)
400+
another(`(-foo + ((bar) / (baz))) + ((23))`, `0 - foo + bar / baz + 23`)
401+
another(`(FOO + ((Bar) / (baZ))) + ((23))`, `FOO + Bar / baZ + 23`)
402402
same(`(foo, bar)`)
403403
another(`((foo, bar),(baz))`, `((foo, bar), baz)`)
404404
same(`(foo, (bar, baz), ((x, y), (z, y), xx))`)
@@ -479,16 +479,16 @@ func TestParseSuccess(t *testing.T) {
479479
another(`with (foo = bar) baz`, `baz`)
480480
another(`with (foo = bar) foo + foo{a="b"}`, `bar + bar{a="b"}`)
481481
another(`with (foo = bar, bar=baz + f()) test`, `test`)
482-
another(`with (ct={job="test"}) a{ct} + ct() + ceil({ct="x"})`, `(a{job="test"} + {job="test"}) + ceil({ct="x"})`)
482+
another(`with (ct={job="test"}) a{ct} + ct() + ceil({ct="x"})`, `a{job="test"} + {job="test"} + ceil({ct="x"})`)
483483
another(`with (ct={job="test", i="bar"}) ct + {ct, x="d"} + foo{ct, ct} + count(1)`,
484-
`(({job="test",i="bar"} + {job="test",i="bar",x="d"}) + foo{job="test",i="bar"}) + count(1)`)
484+
`{job="test",i="bar"} + {job="test",i="bar",x="d"} + foo{job="test",i="bar"} + count(1)`)
485485
another(`with (foo = bar) {__name__=~"foo"}`, `{__name__=~"foo"}`)
486486
another(`with (foo = bar) foo{__name__="foo"}`, `bar`)
487487
another(`with (foo = bar) {__name__="foo", x="y"}`, `bar{x="y"}`)
488488
another(`with (foo(bar) = {__name__!="bar"}) foo(x)`, `{__name__!="bar"}`)
489489
another(`with (foo(bar) = bar{__name__="bar"}) foo(x)`, `x`)
490490
another(`with (foo\-bar(baz) = baz + baz) foo\-bar((x,y))`, `(x, y) + (x, y)`)
491-
another(`with (foo\-bar(baz) = baz + baz) foo\-bar(x*y)`, `(x * y) + (x * y)`)
491+
another(`with (foo\-bar(baz) = baz + baz) foo\-bar(x*y)`, `x * y + x * y`)
492492
another(`with (foo\-bar(baz) = baz + baz) foo\-bar(x\*y)`, `x\*y + x\*y`)
493493
another(`with (foo\-bar(b\ az) = b\ az + b\ az) foo\-bar(x\*y)`, `x\*y + x\*y`)
494494

@@ -521,7 +521,7 @@ func TestParseSuccess(t *testing.T) {
521521
another(`with (ttf = ru(m, n)) ttf`, `(clamp_min(n - clamp_min(m, 0), 0) / clamp_min(n, 0)) * 100`)
522522

523523
// Verify withExpr recursion and forward reference
524-
another(`with (x = x+y, y = x+x) y ^ 2`, `((x + y) + (x + y)) ^ 2`)
524+
another(`with (x = x+y, y = x+x) y ^ 2`, `(x + y + x + y) ^ 2`)
525525
another(`with (f1(x)=ceil(x), ceil(x)=f1(x)^2) f1(foobar)`, `ceil(foobar)`)
526526
another(`with (f1(x)=ceil(x), ceil(x)=f1(x)^2) ceil(foobar)`, `ceil(foobar) ^ 2`)
527527

@@ -533,7 +533,7 @@ func TestParseSuccess(t *testing.T) {
533533
another(`with (x(a) = sum(a) by (b)) x(xx) / x(y)`, `sum(xx) by(b) / sum(y) by(b)`)
534534
another(`with (f(a,f,x)=clamp(x,f,a)) f(f(x,y,z),1,2)`, `clamp(2, 1, clamp(z, y, x))`)
535535
another(`with (f(x)=1+sum(x)) f(foo{bar="baz"})`, `1 + sum(foo{bar="baz"})`)
536-
another(`with (a=foo, y=bar, f(a)= a+a+y) f(x)`, `(x + x) + bar`)
536+
another(`with (a=foo, y=bar, f(a)= a+a+y) f(x)`, `x + x + bar`)
537537
another(`with (f(a, b) = m{a, b}) f({a="x", b="y"}, {c="d"})`, `m{a="x",b="y",c="d"}`)
538538
another(`with (xx={a="x"}, f(a, b) = m{a, b}) f({xx, b="y"}, {c="d"})`, `m{a="x",b="y",c="d"}`)
539539
another(`with (x() = {b="c"}) foo{x}`, `foo{b="c"}`)
@@ -572,7 +572,7 @@ func TestParseSuccess(t *testing.T) {
572572
// Verify nested with exprs
573573
another(`with (f(x) = (with(x=y) x) + x) f(z)`, `y + z`)
574574
another(`with (x=foo) clamp_min(a, with (y=x) y)`, `clamp_min(a, foo)`)
575-
another(`with (x=foo) a * x + (with (y=x) y) / y`, `(a * foo) + (foo / y)`)
575+
another(`with (x=foo) a * x + (with (y=x) y) / y`, `a * foo + foo / y`)
576576
another(`with (x = with (y = foo) y + x) x/x`, `(foo + x) / (foo + x)`)
577577
another(`with (
578578
x = {foo="bar"},
@@ -583,7 +583,7 @@ func TestParseSuccess(t *testing.T) {
583583
)
584584
z(foo) / changes(x)
585585
)
586-
f(a)`, `(a + (foo * m{foo="bar",y="1"})) / changes(a)`)
586+
f(a)`, `(a + foo * m{foo="bar",y="1"}) / changes(a)`)
587587

588588
// complex withExpr
589589
another(`WITH (
@@ -601,7 +601,7 @@ func TestParseSuccess(t *testing.T) {
601601
f(x, y) = x2(x) + x*y + x2(y)
602602
)
603603
f(a, 3)
604-
`, `((a ^ 2) + (a * 3)) + 9`)
604+
`, `a ^ 2 + a * 3 + 9`)
605605
another(`WITH (
606606
x2(x) = x^2,
607607
f(x, y) = x2(x) + x*y + x2(y)
@@ -633,7 +633,7 @@ func TestParseSuccess(t *testing.T) {
633633
another(`with (rate(a,b)=a+b) rate(1,2)`, `3`)
634634
another(`with (now=now(), sum=sum()) x`, `x`)
635635
another(`with (rate(a) = b) c`, `c`)
636-
another(`rate(x) + with (rate(a,b)=a*b) rate(2,b)`, `rate(x) + (2 * b)`)
636+
another(`rate(x) + with (rate(a,b)=a*b) rate(2,b)`, `rate(x) + 2 * b`)
637637
another(`with (sum(a,b)=a+b) sum(c,d)`, `c + d`)
638638

639639
// $__interval and $__rate_interval must be replaced with 1i

prettifier_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ x + sum(y)`)
258258
`WITH (
259259
x = a{b="c"} + WITH (q = we{rt="z"}) q,
260260
)
261-
(abc / x) + WITH (rt = 234 + 234) (2 * rt) + poasdfklkjlkjfdsfjklfdfdsfdsfddfsfd`)
261+
abc / x + WITH (rt = 234 + 234) 2 * rt + poasdfklkjlkjfdsfjklfdfdsfdsfddfsfd`)
262262

263263
// duration replacement in WITH expression
264264
another(`WITH(BAR=1m,x(BAZ)=sum(rate({a="b"}[BAR:BAZ])) offset BAR) x`, `WITH (BAR = 1m, x(BAZ) = sum(rate({a="b"}[BAR:BAZ])) offset BAR) x`)
@@ -275,4 +275,26 @@ x + sum(y)`)
275275
// Verify that exact match __name__ still works
276276
another(`{__name__="foo"}`, `foo`)
277277
another(`{__name__="foo",bar="baz"}`, `foo{bar="baz"}`)
278+
279+
same(`1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10`)
280+
same(`(1 + 2) * (3 + 4)`)
281+
same(`1 + 2 + foo + bar + baz`)
282+
283+
another(`a offset 5m + b @ start()`, `(a offset 5m) + (b @ start())`)
284+
another(`(1 - (node_memory_MemFree_bytes + node_memory_Cached_bytes + node_memory_Buffers_bytes + node_memory_SReclaimable_bytes) / node_memory_MemTotal_bytes) * 100`,
285+
`(
286+
1
287+
-
288+
(
289+
node_memory_MemFree_bytes + node_memory_Cached_bytes
290+
+
291+
node_memory_Buffers_bytes
292+
+
293+
node_memory_SReclaimable_bytes
294+
)
295+
/
296+
node_memory_MemTotal_bytes
297+
)
298+
*
299+
100`)
278300
}

utils_example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ func ExampleExpandWithExprs() {
2424
fmt.Printf("%s\n", pql)
2525

2626
// Output:
27-
// 100 * (disk_free_bytes{job="$job",instance="$instance"} / disk_total_bytes{job="$job",instance="$instance"})
27+
// 100 * disk_free_bytes{job="$job",instance="$instance"} / disk_total_bytes{job="$job",instance="$instance"}
2828
}

0 commit comments

Comments
 (0)