From fa618fe9a36a58d225776f5bf96dcad07ee9bbdd Mon Sep 17 00:00:00 2001 From: Dan Hansen Date: Thu, 1 Feb 2024 15:07:55 -0800 Subject: [PATCH 1/4] [Filter] Fix syntax error that occurred for some queries using subselects or QUALIFY --- internal/formatter.go | 17 +++++++++++++++-- query_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/internal/formatter.go b/internal/formatter.go index b5e776d..119a44d 100644 --- a/internal/formatter.go +++ b/internal/formatter.go @@ -3,6 +3,7 @@ package internal import ( "context" "fmt" + "regexp" "strings" "github.com/goccy/go-json" @@ -686,7 +687,10 @@ func (n *ColumnHolderNode) FormatSQL(ctx context.Context) (string, error) { return "", nil } +var tokensAfterFromClause = [...]string{"WHERE", "GROUP BY", "HAVING", "QUALIFY", "WINDOW", "ORDER BY", "COLLATE"} + func (n *FilterScanNode) FormatSQL(ctx context.Context) (string, error) { + removeExpressions := regexp.MustCompile(`\(.+?\)`) if n.node == nil { return "", nil } @@ -704,8 +708,17 @@ func (n *FilterScanNode) FormatSQL(ctx context.Context) (string, error) { return fmt.Sprintf("%s HAVING %s", input, filter), nil } } - if strings.Contains(input, "WHERE") && input[len(input)-1] != ')' { - // expected to qualify clause + currentQuery := string(removeExpressions.ReplaceAllString(input, "")) + + // Qualify the statement if the input is not wrapped in parens + queryWrappedInParens := currentQuery == "" + containsTokens := false + // or the input contains a token that would result in a syntax error + for _, token := range tokensAfterFromClause { + containsTokens = containsTokens || strings.Contains(currentQuery, token) + } + + if !queryWrappedInParens && containsTokens { return fmt.Sprintf("( %s ) WHERE %s", input, filter), nil } return fmt.Sprintf("%s WHERE %s", input, filter), nil diff --git a/query_test.go b/query_test.go index 515595e..089cfdc 100644 --- a/query_test.go +++ b/query_test.go @@ -2781,6 +2781,35 @@ FROM Produce WHERE Produce.category = 'vegetable' QUALIFY rank <= 3`, {"cabbage", int64(3)}, }, }, + // Regression test goccy/go-zetasqlite#150 + { + name: "qualify group", + query: ` + WITH produce AS ( + SELECT 'kale' AS item, 23 AS purchases + ) + SELECT item, sum(purchases) + FROM produce + GROUP BY item + QUALIFY ROW_NUMBER() OVER (PARTITION BY item ORDER BY item) = 1 + `, + expectedRows: [][]interface{}{{"kale", int64(23)}}, + }, + // Regression test goccy/go-zetasqlite#147 + { + name: "subselect qualifier", + query: ` + WITH produce AS (SELECT 'banana' AS item, 3 AS purchases), + toks AS ( + SELECT item FROM ( + SELECT * FROM produce + WHERE item = 'banana' + ) sub + WHERE purchases = 3 + ) + SELECT * FROM toks;`, + expectedRows: [][]interface{}{{"banana"}}, + }, { name: "qualify direct", query: ` From b1d23e5a4a0dc47ece55d1a5806206543a49928c Mon Sep 17 00:00:00 2001 From: Dan Hansen Date: Thu, 1 Feb 2024 15:43:14 -0800 Subject: [PATCH 2/4] comment update --- internal/formatter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/formatter.go b/internal/formatter.go index 119a44d..3f2bcf6 100644 --- a/internal/formatter.go +++ b/internal/formatter.go @@ -713,7 +713,7 @@ func (n *FilterScanNode) FormatSQL(ctx context.Context) (string, error) { // Qualify the statement if the input is not wrapped in parens queryWrappedInParens := currentQuery == "" containsTokens := false - // or the input contains a token that would result in a syntax error + // and the input contains a token that would result in a syntax error for _, token := range tokensAfterFromClause { containsTokens = containsTokens || strings.Contains(currentQuery, token) } From ff780fdf717add9e943ca701a187009bbdce8658 Mon Sep 17 00:00:00 2001 From: Dan Hansen Date: Sat, 9 Mar 2024 10:18:45 -0800 Subject: [PATCH 3/4] Update formatter.go --- internal/formatter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/formatter.go b/internal/formatter.go index 3f2bcf6..848af4a 100644 --- a/internal/formatter.go +++ b/internal/formatter.go @@ -688,9 +688,9 @@ func (n *ColumnHolderNode) FormatSQL(ctx context.Context) (string, error) { } var tokensAfterFromClause = [...]string{"WHERE", "GROUP BY", "HAVING", "QUALIFY", "WINDOW", "ORDER BY", "COLLATE"} +var removeExpressions := regexp.MustCompile(`\(.+?\)`) func (n *FilterScanNode) FormatSQL(ctx context.Context) (string, error) { - removeExpressions := regexp.MustCompile(`\(.+?\)`) if n.node == nil { return "", nil } From 5fddf4aa68415234883b2435671c3d0efeca6b4c Mon Sep 17 00:00:00 2001 From: Dan Hansen Date: Sat, 9 Mar 2024 10:36:30 -0800 Subject: [PATCH 4/4] Update formatter.go --- internal/formatter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/formatter.go b/internal/formatter.go index 848af4a..e4ad65e 100644 --- a/internal/formatter.go +++ b/internal/formatter.go @@ -688,7 +688,7 @@ func (n *ColumnHolderNode) FormatSQL(ctx context.Context) (string, error) { } var tokensAfterFromClause = [...]string{"WHERE", "GROUP BY", "HAVING", "QUALIFY", "WINDOW", "ORDER BY", "COLLATE"} -var removeExpressions := regexp.MustCompile(`\(.+?\)`) +var removeExpressions = regexp.MustCompile(`\(.+?\)`) func (n *FilterScanNode) FormatSQL(ctx context.Context) (string, error) { if n.node == nil {