Skip to content

Commit 4221934

Browse files
author
awitas
committed
updated parser
1 parent b707428 commit 4221934

11 files changed

Lines changed: 170 additions & 5 deletions

File tree

expr/parent.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package expr
22

3+
import "github.com/viant/sqlparser/node"
4+
35
type Parenthesis struct {
46
Raw string
7+
X node.Node
58
}
69

710
func NewParenthesis(raw string) *Parenthesis {

expr/raw.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package expr
22

3+
import "github.com/viant/sqlparser/node"
4+
35
type Raw struct {
46
Raw string
7+
X node.Node
58
}
69

710
func NewRaw(raw string) *Raw {

lex.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ const (
5252
rangeOperator
5353
windowTokenCode
5454
literalCode
55+
exprToken
5556
keyTokenCode
5657
notNullToken
5758
createTableToken
5859
defaultToken
5960
ifNotExistsToken
61+
ifExistsToken
6062
dropTableToken
6163
deleteCode
6264
)
@@ -130,12 +132,17 @@ var identifierMatcher = parsly.NewToken(identifierCode, "IDENT", smatcher.NewIde
130132
var selectorMatcher = parsly.NewToken(selectorTokenCode, "SELECTOR", smatcher.NewSelector())
131133
var placeholderMatcher = parsly.NewToken(placeholderTokenCode, "SELECTOR", smatcher.NewPlaceholder())
132134
var literalMatcher = parsly.NewToken(literalCode, "LITERAL", matcher.NewNop())
135+
var exprMatcher = parsly.NewToken(exprToken, ",EXPR", matcher.NewNop())
136+
133137
var deleteMatcher = parsly.NewToken(deleteCode, "DELETE", matcher.NewFragmentsFold([]byte("delete")))
134138
var notNullMatcher = parsly.NewToken(notNullToken, "NOT NULL", matcher.NewSpacedSet([]string{
135139
"not null"}, &option.Case{}))
136140
var ifNotExistsMatcher = parsly.NewToken(ifNotExistsToken, "IF NOT EXISTS", matcher.NewSpacedSet([]string{
137141
"if not exists"}, &option.Case{}))
138142

143+
var ifExistsMatcher = parsly.NewToken(ifExistsToken, "IF EXISTS", matcher.NewSpacedSet([]string{
144+
"if exists"}, &option.Case{}))
145+
139146
var createTableMatcher = parsly.NewToken(createTableToken, "CREATE TABLE", matcher.NewSpacedSet([]string{
140147
"create table"}, &option.Case{}))
141148

list.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ func parseSelectListItem(cursor *parsly.Cursor, list *query.List) error {
4343
}
4444
fallthrough
4545
case nextCode:
46-
return parseSelectListItem(cursor, list)
46+
count := len(*list)
47+
if err = parseSelectListItem(cursor, list); err != nil {
48+
return err
49+
}
50+
if count == len(*list) {
51+
return cursor.NewError(exprMatcher)
52+
}
4753
}
4854
return nil
4955
}

operand.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func expectOperand(cursor *parsly.Cursor) (node.Node, error) {
4444
commentBlockMatcher,
4545
)
4646
pos := cursor.Pos
47+
4748
switch match.Code {
4849
case selectorTokenCode, placeholderTokenCode:
4950

@@ -107,7 +108,7 @@ func expectOperand(cursor *parsly.Cursor) (node.Node, error) {
107108
return unary, nil
108109

109110
case asKeyword, orderByKeyword, onKeyword, fromKeyword, whereKeyword, joinToken, groupByKeyword, havingKeyword, windowTokenCode, nextCode, commentBlock:
110-
cursor.Pos -= pos
111+
cursor.Pos = pos
111112
}
112113
return nil, nil
113114
}

query.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func ParseQuery(SQL string) (*query.Select, error) {
1515
cursor := parsly.NewCursor("", []byte(SQL), 0)
1616
err := parseQuery(cursor, result)
1717
if err != nil {
18-
return result, fmt.Errorf("%s", SQL)
18+
return result, fmt.Errorf("%w %s", err, SQL)
1919
}
2020
return result, err
2121
}
@@ -50,6 +50,7 @@ func parseQuery(cursor *parsly.Cursor, dest *query.Select) error {
5050
}
5151

5252
match = cursor.MatchAfterOptional(whitespaceMatcher, fromKeywordMatcher)
53+
pos := cursor.Pos
5354
switch match.Code {
5455
case fromKeyword:
5556
dest.From = query.From{}
@@ -58,13 +59,21 @@ func parseQuery(cursor *parsly.Cursor, dest *query.Select) error {
5859
case selectorTokenCode:
5960
dest.From.X = expr.NewSelector(match.Text(cursor))
6061
case parenthesesCode:
61-
dest.From.X = expr.NewRaw(match.Text(cursor))
62+
rawNode := expr.NewRaw(match.Text(cursor))
63+
dest.From.X = rawNode
64+
rawExpr := trimEnclosure(rawNode.Raw)
65+
rawParser := parsly.NewCursor(cursor.Path, []byte(rawExpr), pos)
66+
subSelect := &query.Select{}
67+
if err := parseQuery(rawParser, subSelect); err != nil {
68+
return fmt.Errorf("invalid subquery: %w, %s", err, rawExpr)
69+
}
70+
rawNode.X = subSelect
71+
6272
}
6373
dest.From.Alias = discoverAlias(cursor)
6474
dest.From.Comments = matchComment(cursor)
6575

6676
dest.Joins = make([]*query.Join, 0)
67-
6877
match = cursor.MatchAfterOptional(whitespaceMatcher, joinMatcher, whereKeywordMatcher, groupByMatcher, havingKeywordMatcher, orderByKeywordMatcher, windowMatcher)
6978
if match.Code == parsly.EOF {
7079
return nil

query/select.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,23 @@ type Select struct {
1717
Offset *expr.Literal
1818
Kind string
1919
}
20+
21+
func (s *Select) IsNested() bool {
22+
if s.From.X == nil {
23+
return false
24+
}
25+
_, ok := s.From.X.(*expr.Raw)
26+
return ok
27+
}
28+
29+
func (s *Select) NestedSelect() *Select {
30+
if s.From.X == nil {
31+
return nil
32+
}
33+
raw, ok := s.From.X.(*expr.Raw)
34+
if !ok {
35+
return nil
36+
}
37+
result, _ := raw.X.(*Select)
38+
return result
39+
}

query_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@ func TestParseSelect(t *testing.T) {
1515
description string
1616
SQL string
1717
expect string
18+
hasError bool
1819
}{
1920

21+
{
22+
description: "error - extra coma",
23+
SQL: `SELECT TOUPPER(name) AS Name, FROM user u`,
24+
hasError: true,
25+
},
2026
{
2127
description: "fun call",
2228
SQL: `SELECT TOUPPER(name) AS Name FROM user u`,
2329
expect: `SELECT TOUPPER(name) AS Name FROM user u`,
2430
},
31+
2532
{
2633
description: "cast call",
2734
SQL: `SELECT CAST(name AS TEXT) AS Name FROM user u`,
@@ -152,7 +159,12 @@ func TestParseSelect(t *testing.T) {
152159

153160
for _, testCase := range testCases {
154161
query, err := ParseQuery(testCase.SQL)
162+
if testCase.hasError {
163+
assert.NotNilf(t, err, testCase.description)
164+
continue
165+
}
155166
if !assert.Nil(t, err) {
167+
fmt.Println(err)
156168
fmt.Printf("%v\n", testCase.SQL)
157169
continue
158170
}

stringify.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ func stringify(n node.Node, builder *bytes.Buffer) {
2525
panic("node was nill")
2626
}
2727
switch actual := n.(type) {
28+
case string:
29+
builder.WriteString(actual)
2830
case *query.Select:
2931
builder.WriteString("SELECT ")
3032
stringify(actual.List, builder)

table.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,105 @@ import (
44
"fmt"
55
"github.com/viant/parsly"
66
"github.com/viant/sqlparser/column"
7+
del "github.com/viant/sqlparser/delete"
8+
"github.com/viant/sqlparser/expr"
9+
"github.com/viant/sqlparser/insert"
10+
"github.com/viant/sqlparser/node"
11+
"github.com/viant/sqlparser/query"
712
"github.com/viant/sqlparser/table"
13+
"github.com/viant/sqlparser/update"
814
)
915

16+
//TableName returns main table name
17+
func TableName(node node.Node) string {
18+
switch actual := node.(type) {
19+
case *query.Select:
20+
return queryTableName(actual)
21+
case *insert.Statement:
22+
return trimEnclosure(actual.Target.X)
23+
case *update.Statement:
24+
return trimEnclosure(actual.Target.X)
25+
case *del.Statement:
26+
return trimEnclosure(actual.Target.X)
27+
case *table.Create:
28+
return trimEnclosure(actual.Spec.Name)
29+
case *table.Drop:
30+
return trimEnclosure(actual.Name)
31+
}
32+
return ""
33+
}
34+
35+
func queryTableName(sel *query.Select) string {
36+
if sel.From.X == nil {
37+
return ""
38+
}
39+
switch actual := sel.From.X.(type) {
40+
case *expr.Ident:
41+
return actual.Name
42+
case *expr.Selector:
43+
return trimEnclosure(actual)
44+
case *expr.Parenthesis:
45+
raw := trimEnclosure(actual.Raw)
46+
if sel, _ := ParseQuery(raw); sel != nil {
47+
actual.X = sel
48+
return TableName(sel)
49+
}
50+
case *expr.Raw:
51+
if actual.X != nil {
52+
return TableName(actual.X)
53+
}
54+
default:
55+
panic(fmt.Sprintf("not supported:%T ", actual))
56+
}
57+
return ""
58+
}
59+
60+
func trimEnclosure(node node.Node) string {
61+
if node == nil {
62+
return ""
63+
}
64+
var name string
65+
var ok bool
66+
if name, ok = node.(string); !ok {
67+
name = Stringify(node)
68+
}
69+
switch name[0] {
70+
case '`', '"', '[', '\'', '(':
71+
name = name[1 : len(name)-1]
72+
}
73+
return name
74+
}
75+
76+
func ParseDropTable(SQL string) (*table.Drop, error) {
77+
result := &table.Drop{}
78+
SQL = removeSQLComments(SQL)
79+
cursor := parsly.NewCursor("", []byte(SQL), 0)
80+
err := parseDropTable(cursor, result)
81+
if err != nil {
82+
return result, fmt.Errorf("%s", SQL)
83+
}
84+
return result, err
85+
}
86+
87+
func parseDropTable(cursor *parsly.Cursor, dest *table.Drop) error {
88+
match := cursor.MatchAfterOptional(whitespaceMatcher, dropTableMatcher)
89+
if match.Code != dropTableToken {
90+
return cursor.NewError(createTableMatcher)
91+
}
92+
if match = cursor.MatchOne(whitespaceMatcher); match.Code != whitespaceCode {
93+
return cursor.NewError(whitespaceMatcher)
94+
}
95+
if match = cursor.MatchOne(ifExistsMatcher); match.Code == ifExistsToken {
96+
dest.IfExists = true
97+
}
98+
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
99+
if match.Code != identifierCode {
100+
return cursor.NewError(ifNotExistsMatcher)
101+
}
102+
dest.Name = match.Text(cursor)
103+
return nil
104+
}
105+
10106
func ParseCreateTable(SQL string) (*table.Create, error) {
11107
result := &table.Create{}
12108
SQL = removeSQLComments(SQL)

0 commit comments

Comments
 (0)