Skip to content

Commit f06afed

Browse files
Merge remote-tracking branch 'origin/main'
2 parents 9e7ec64 + ecacfec commit f06afed

5 files changed

Lines changed: 43 additions & 16 deletions

File tree

lex.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const (
6969
dropIndexToken
7070
deleteCode
7171
withKeyword
72+
recursiveKeyword
7273
unionKeyword
7374
globalKeyword
7475
registerKeyword
@@ -133,7 +134,7 @@ var insertIntoKeywordMatcher = parsly.NewToken(insertIntoKeyword, "INSERT INTO",
133134

134135
var insertValesKeywordMatcher = parsly.NewToken(insertValuesKeyword, "VALUES", matcher.NewKeyword("values", &option.Case{}))
135136

136-
var binaryOperatorMatcher = parsly.NewToken(binaryOperator, "binary OPERATOR", matcher.NewSpacedSet([]string{"+", "!=", ">=", "<=", "=", "-", ">", "<", "*", "/", "in", "not in", "is not", "is", "like"}, &option.Case{}))
137+
var binaryOperatorMatcher = parsly.NewToken(binaryOperator, "binary OPERATOR", matcher.NewSpacedSet([]string{"+", "!=", "<>", ">=", "<=", "=", "-", ">", "<", "*", "/", "in", "not in", "is not", "is", "like"}, &option.Case{}))
137138
var assignOperatorMatcher = parsly.NewToken(assignOperator, "assign OPERATOR", matcher.NewSpacedSet([]string{"="}, &option.Case{}))
138139

139140
var logicalOperatorMatcher = parsly.NewToken(logicalOperator, "AND|OR", matcher.NewSet([]string{"and", "or"}, &option.Case{}))
@@ -156,6 +157,7 @@ var placeholderMatcher = parsly.NewToken(placeholderTokenCode, "SELECTOR", smatc
156157
var literalMatcher = parsly.NewToken(literalCode, "LITERAL", matcher.NewNop())
157158

158159
var withKeywordMatcher = parsly.NewToken(withKeyword, "WITH", matcher.NewKeyword("with", &option.Case{}))
160+
var recursiveKeywordMatcher = parsly.NewToken(recursiveKeyword, "RECURSIVE", matcher.NewKeyword("recursive", &option.Case{}))
159161
var unionMatcher = parsly.NewToken(unionKeyword, "UNION|UNION ALL", matcher.NewSpacedSet([]string{
160162
"union all",
161163
"union",

query.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,20 @@ beginMatch:
4949
if len(dest.WithSelects) > 0 {
5050
return cursor.NewError(asKeywordMatcher, selectorMatcher)
5151
}
52+
allowRecursive := true
5253
With:
5354
withSelect := &query.WithSelect{X: &query.Select{}}
5455
dest.WithSelects = append(dest.WithSelects, withSelect)
55-
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
56+
if allowRecursive {
57+
match = cursor.MatchAfterOptional(whitespaceMatcher, recursiveKeywordMatcher, identifierMatcher)
58+
if match.Code == recursiveKeyword {
59+
dest.WithRecursive = true
60+
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
61+
}
62+
allowRecursive = false
63+
} else {
64+
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
65+
}
5666
if match.Code != identifierCode {
5767
return cursor.NewError(identifierMatcher)
5868
}

query/select.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@ import (
77
type (
88
//Select represent a select
99
Select struct {
10-
List List
11-
From From
12-
Joins []*Join
13-
Qualify *expr.Qualify
14-
GroupBy List
15-
Having *expr.Qualify
16-
OrderBy List
17-
Window *expr.Raw
18-
Limit *expr.Literal
19-
Offset *expr.Literal
20-
Kind string
21-
Union *Union
22-
WithSelects WithSelects
10+
List List
11+
From From
12+
Joins []*Join
13+
Qualify *expr.Qualify
14+
GroupBy List
15+
Having *expr.Qualify
16+
OrderBy List
17+
Window *expr.Raw
18+
Limit *expr.Literal
19+
Offset *expr.Literal
20+
Kind string
21+
Union *Union
22+
WithRecursive bool
23+
WithSelects WithSelects
2324
}
2425

2526
//WithSelects represents with selects

query_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ func TestParseSelect(t *testing.T) {
7272
SELECT p.*, v.* FROM p JOIN v ON p.VENDOR_ID = v.ID`,
7373
expect: `WITH p AS (SELECT * FROM product), v AS (SELECT * FROM vendor) SELECT p.*, v.* FROM p JOIN v ON p.VENDOR_ID = v.ID`,
7474
},
75+
{
76+
description: "with recursive syntax",
77+
SQL: `WITH RECURSIVE sub AS (SELECT 1 AS id FROM DUAL UNION ALL SELECT id + 1 AS id FROM sub WHERE id < 5) SELECT t.id FROM sub t WHERE 1=1`,
78+
expect: `WITH RECURSIVE sub AS (SELECT 1 AS id FROM DUAL UNION ALL SELECT id + 1 AS id FROM sub WHERE id < 5) SELECT t.id FROM sub t WHERE 1 = 1`,
79+
},
80+
{
81+
description: "with recursive multi-cte",
82+
SQL: `WITH RECURSIVE sub AS (SELECT t.ID, t.PARENT_ID, t.NAME, t.ID AS LEVEL_ID FROM CI_TAXONOMY t UNION ALL SELECT c.ID, c.PARENT_ID, c.NAME, sub.LEVEL_ID FROM CI_TAXONOMY c JOIN sub ON c.PARENT_ID = sub.ID), level AS (SELECT t.ID, t.PROVIDER_ID, t.PARENT_ID, t.NAME, t.PROVIDER_KEY, t.DESCRIPTION, t.PROVIDER_FEE_ID, t.ADVERTISER_ID, t.CREATED, t.UPDATED, t.TARGETABLE, t.STATUS FROM CI_TAXONOMY t JOIN (SELECT DISTINCT LEVEL_ID FROM sub WHERE LOWER(sub.NAME) LIKE CONCAT('%', LOWER($Name), '%')) hit ON hit.LEVEL_ID = t.ID), parents AS (SELECT l.ID AS NODE_ID, p.ID AS CUR_ID, p.PARENT_ID AS NEXT_ID, p.NAME AS NAME, 1 AS DEPTH FROM level l JOIN CI_TAXONOMY p ON p.ID = l.PARENT_ID WHERE l.PARENT_ID IS NOT NULL AND l.PARENT_ID <> 0 UNION ALL SELECT parents.NODE_ID, p.ID AS CUR_ID, p.PARENT_ID AS NEXT_ID, p.NAME AS NAME, parents.DEPTH + 1 AS DEPTH FROM parents JOIN CI_TAXONOMY p ON p.ID = parents.NEXT_ID WHERE parents.NEXT_ID IS NOT NULL AND parents.NEXT_ID <> 0), parent_paths AS (SELECT NODE_ID, JSON_ARRAYAGG(NAME ORDER BY DEPTH ASC) AS PARENT_PATH FROM parents GROUP BY NODE_ID) SELECT l.ID, l.PROVIDER_ID, l.PARENT_ID, l.NAME, l.PROVIDER_KEY, l.DESCRIPTION, l.PROVIDER_FEE_ID, l.ADVERTISER_ID, l.CREATED, l.UPDATED, l.TARGETABLE, l.STATUS, (CASE l.Status WHEN 0 THEN 'Inactive' WHEN 1 THEN 'Active' WHEN 2 THEN 'Archived' ELSE '' END) AS STATUS_NAME, COALESCE(pp.PARENT_PATH, JSON_ARRAY()) AS PARENT_PATH, '' AS PROVIDER_THIRD_PARTY FROM level l LEFT JOIN parent_paths pp ON pp.NODE_ID = l.ID`,
83+
expect: `WITH RECURSIVE sub AS (SELECT t.ID, t.PARENT_ID, t.NAME, t.ID AS LEVEL_ID FROM CI_TAXONOMY t UNION ALL SELECT c.ID, c.PARENT_ID, c.NAME, sub.LEVEL_ID FROM CI_TAXONOMY c JOIN sub ON c.PARENT_ID = sub.ID), level AS (SELECT t.ID, t.PROVIDER_ID, t.PARENT_ID, t.NAME, t.PROVIDER_KEY, t.DESCRIPTION, t.PROVIDER_FEE_ID, t.ADVERTISER_ID, t.CREATED, t.UPDATED, t.TARGETABLE, t.STATUS FROM CI_TAXONOMY t JOIN (SELECT DISTINCT LEVEL_ID FROM sub WHERE LOWER(sub.NAME) LIKE CONCAT('%', LOWER($Name), '%')) hit ON hit.LEVEL_ID = t.ID), parents AS (SELECT l.ID AS NODE_ID, p.ID AS CUR_ID, p.PARENT_ID AS NEXT_ID, p.NAME AS NAME, 1 AS DEPTH FROM level l JOIN CI_TAXONOMY p ON p.ID = l.PARENT_ID WHERE l.PARENT_ID IS NOT NULL AND l.PARENT_ID <> 0 UNION ALL SELECT parents.NODE_ID, p.ID AS CUR_ID, p.PARENT_ID AS NEXT_ID, p.NAME AS NAME, parents.DEPTH + 1 AS DEPTH FROM parents JOIN CI_TAXONOMY p ON p.ID = parents.NEXT_ID WHERE parents.NEXT_ID IS NOT NULL AND parents.NEXT_ID <> 0), parent_paths AS (SELECT NODE_ID, JSON_ARRAYAGG(NAME ORDER BY DEPTH ASC) AS PARENT_PATH FROM parents GROUP BY NODE_ID) SELECT l.ID, l.PROVIDER_ID, l.PARENT_ID, l.NAME, l.PROVIDER_KEY, l.DESCRIPTION, l.PROVIDER_FEE_ID, l.ADVERTISER_ID, l.CREATED, l.UPDATED, l.TARGETABLE, l.STATUS, (CASE l.Status WHEN 0 THEN 'Inactive' WHEN 1 THEN 'Active' WHEN 2 THEN 'Archived' ELSE '' END) AS STATUS_NAME, COALESCE(pp.PARENT_PATH, JSON_ARRAY()) AS PARENT_PATH, '' AS PROVIDER_THIRD_PARTY FROM level l LEFT JOIN parent_paths pp ON pp.NODE_ID = l.ID`,
84+
},
7585
{
7686
description: "with join alias",
7787
SQL: `WITH audiences AS (SELECT 1 AS id)

stringify.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ func stringify(n node.Node, builder *bytes.Buffer) {
3232
builder.WriteString(actual)
3333
case *query.Select:
3434
if len(actual.WithSelects) > 0 {
35-
builder.WriteString("WITH ")
35+
if actual.WithRecursive {
36+
builder.WriteString("WITH RECURSIVE ")
37+
} else {
38+
builder.WriteString("WITH ")
39+
}
3640
for i, withSel := range actual.WithSelects {
3741
if i > 0 {
3842
builder.WriteString(", ")

0 commit comments

Comments
 (0)