Skip to content

Commit

Permalink
Merge pull request #650 from Altinity/issue-648
Browse files Browse the repository at this point in the history
fix corner AST for brackets
  • Loading branch information
Slach authored Oct 27, 2024
2 parents 8b437f1 + 202249d commit c053164
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 35 deletions.
14 changes: 7 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.23
require (
github.com/andybalholm/brotli v1.1.1
github.com/dlclark/regexp2 v1.11.4
github.com/grafana/grafana-plugin-sdk-go v0.255.0
github.com/grafana/grafana-plugin-sdk-go v0.258.0
github.com/klauspost/compress v1.17.11
github.com/stretchr/testify v1.9.0
golang.org/x/sync v0.8.0
Expand All @@ -18,11 +18,11 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/chromedp/cdproto v0.0.0-20241014181340-cb3a7a1d51d7 // indirect
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/elazarl/goproxy v0.0.0-20240909085733-6741dbfc16a1 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/getkin/kin-openapi v0.128.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
Expand All @@ -41,7 +41,7 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-plugin v1.6.1 // indirect
github.com/hashicorp/go-plugin v1.6.2 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand All @@ -65,7 +65,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.0 // indirect
github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
Expand All @@ -92,8 +92,8 @@ require (
golang.org/x/text v0.19.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/grpc v1.67.1 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
Expand Down
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071 h1:RdCf9hH3xq5vJi
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/cdproto v0.0.0-20241014181340-cb3a7a1d51d7 h1:VDBgUGgdCBw9lTKwp0KPExhnqmGfGVJQTER2MehoICk=
github.com/chromedp/cdproto v0.0.0-20241014181340-cb3a7a1d51d7/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb h1:noKVm2SsG4v0Yd0lHNtFYc9EUxIVvrr4kJ6hM8wvIYU=
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
Expand All @@ -47,6 +49,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
Expand Down Expand Up @@ -90,6 +94,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.250.0 h1:9EBucp9jLqMx2b8NTlOXH+4OuQW
github.com/grafana/grafana-plugin-sdk-go v0.250.0/go.mod h1:gCGN9kHY3KeX4qyni3+Kead38Q+85pYOrsDcxZp6AIk=
github.com/grafana/grafana-plugin-sdk-go v0.255.0 h1:e9bmDTdpR6Ho9ru+UhyVoWguVSSGFgInRjOsl4AftBQ=
github.com/grafana/grafana-plugin-sdk-go v0.255.0/go.mod h1:sE25SkFQSj8DwX4qkF7w++6KL6mzOXo1ycDlk9GgFGw=
github.com/grafana/grafana-plugin-sdk-go v0.258.0 h1:rWsaD+5wuGUSNr9fFnSwS6t/jcRtAoEJ51pIR9bbPNs=
github.com/grafana/grafana-plugin-sdk-go v0.258.0/go.mod h1:jN19FbzhAcPTLPIy31X5nvx38rR5eoD/1rASiip0GBY=
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
Expand All @@ -106,6 +112,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=
github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=
github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog=
github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
Expand Down Expand Up @@ -189,6 +197,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA=
github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
Expand Down Expand Up @@ -359,10 +369,14 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U=
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4=
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw=
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
Expand Down
65 changes: 53 additions & 12 deletions pkg/eval_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,7 @@ func newEvalAST(isObj bool) *EvalAST {
var obj map[string]interface{}
var arr []interface{}
if isObj {
obj = make(map[string]interface{}, 0)
obj = make(map[string]interface{})
} else {
arr = make([]interface{}, 0)
}
Expand Down Expand Up @@ -1826,21 +1826,62 @@ func toAST(s string) (*EvalAST, error) {
return scanner.toAST()
}

func isClosured(argument string) bool {
var bracketsQueue []rune
for _, v := range argument {
switch v {
case '(':
bracketsQueue = append(bracketsQueue, v)
case ')':
if 0 < len(bracketsQueue) && bracketsQueue[len(bracketsQueue)-1] == '(' {
bracketsQueue = bracketsQueue[:len(bracketsQueue)-1]
} else {
// isClosured checks if a string has properly balanced brackets while ignoring brackets within quotes
// https://github.com/Altinity/clickhouse-grafana/issues/648
func isClosured(str string) bool {
stack := make([]rune, 0)
isInQuote := false
var quoteType rune

openBrackets := map[rune]rune{
'(': ')',
'[': ']',
'{': '}',
}

closeBrackets := map[rune]rune{
')': '(',
']': '[',
'}': '{',
}

runes := []rune(str)
for i := 0; i < len(runes); i++ {
char := runes[i]

// Handle quotes
if (char == '\'' || char == '"' || char == '`') && (i == 0 || runes[i-1] != '\\') {
if !isInQuote {
isInQuote = true
quoteType = char
} else if char == quoteType {
isInQuote = false
quoteType = 0
}
continue
}

// Skip characters inside quotes
if isInQuote {
continue
}

// Handle brackets
if _, ok := openBrackets[char]; ok {
stack = append(stack, char)
} else if closingPair, ok := closeBrackets[char]; ok {
if len(stack) == 0 {
return false
}
lastOpen := stack[len(stack)-1]
stack = stack[:len(stack)-1] // pop
if lastOpen != closingPair {
return false
}
}
}
return len(bracketsQueue) == 0

return len(stack) == 0
}

func betweenBraces(query string) string {
Expand Down
125 changes: 125 additions & 0 deletions pkg/eval_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1850,3 +1850,128 @@ func TestTableMacroProperlyEscaping(t *testing.T) {

r.Equal(expQuery, actualQuery, description+" unexpected result")
}

// https://github.com/Altinity/clickhouse-grafana/issues/648
func TestIsClosured(t *testing.T) {
// Simple brackets test cases
t.Run("handles simple brackets", func(t *testing.T) {
testCases := []struct {
input string
expected bool
}{
{"(test)", true},
{"[test]", true},
{"{test}", true},
}

for _, tc := range testCases {
result := isClosured(tc.input)
if result != tc.expected {
t.Errorf("isClosured(%q) = %v; want %v", tc.input, result, tc.expected)
}
}
})

// Nested brackets test cases
t.Run("handles nested brackets", func(t *testing.T) {
testCases := []struct {
input string
expected bool
}{
{"({[test]})", true},
{"({[test}])", false},
}

for _, tc := range testCases {
result := isClosured(tc.input)
if result != tc.expected {
t.Errorf("isClosured(%q) = %v; want %v", tc.input, result, tc.expected)
}
}
})

// Quotes test cases
t.Run("handles quotes correctly", func(t *testing.T) {
testCases := []struct {
input string
expected bool
}{
{"'(not a bracket)'", true},
{"\"[also not a bracket]\"", true},
{"`{template literal}`", true},
}

for _, tc := range testCases {
result := isClosured(tc.input)
if result != tc.expected {
t.Errorf("isClosured(%q) = %v; want %v", tc.input, result, tc.expected)
}
}
})

// Escaped quotes test cases
t.Run("handles escaped quotes", func(t *testing.T) {
testCases := []struct {
input string
expected bool
}{
{"''(this is a real bracket)'", true},
{"\\'(this is a bracket after escaped quotes)", true},
}

for _, tc := range testCases {
result := isClosured(tc.input)
if result != tc.expected {
t.Errorf("isClosured(%q) = %v; want %v", tc.input, result, tc.expected)
}
}
})

// Provided test cases
t.Run("handles provided test cases", func(t *testing.T) {
testCases := []struct {
input string
expected bool
}{
{"('('+test)", true},
{"[\"(\"+test+\"]]\"] ", true},
{"('('+test+']]')", true},
{"'('+test ]", false},
{"]['('+test]", false},
}

for _, tc := range testCases {
result := isClosured(tc.input)
if result != tc.expected {
t.Errorf("isClosured(%q) = %v; want %v", tc.input, result, tc.expected)
}
}
})

// Empty input test case
t.Run("handles empty input", func(t *testing.T) {
result := isClosured("")
if !result {
t.Error("isClosured(\"\") = false; want true")
}
})

// Unmatched brackets test cases
t.Run("handles unmatched brackets", func(t *testing.T) {
testCases := []struct {
input string
expected bool
}{
{"(((", false},
{")))", false},
{"((())", false},
}

for _, tc := range testCases {
result := isClosured(tc.input)
if result != tc.expected {
t.Errorf("isClosured(%q) = %v; want %v", tc.input, result, tc.expected)
}
}
})
}
Loading

0 comments on commit c053164

Please sign in to comment.