Skip to content

Commit

Permalink
fix #648, golang part
Browse files Browse the repository at this point in the history
  • Loading branch information
Slach committed Oct 27, 2024
1 parent a97f6d3 commit d9d158e
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 12 deletions.
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)
}
}
})
}

0 comments on commit d9d158e

Please sign in to comment.