Skip to content

Commit 5b3b5b7

Browse files
committed
added index kind
1 parent 3e49110 commit 5b3b5b7

9 files changed

Lines changed: 278 additions & 11 deletions

File tree

go.sum

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
32
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
43
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
54
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7-
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
8-
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
9-
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
10-
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
11-
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
12-
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
5+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
136
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
14-
github.com/viant/parsly v0.3.2-0.20231120160314-11ec855ce00a h1:H+7xS8C8Ywjxc0EfiKL9m+A7AF6JqkJKu5W+7zus/W0=
15-
github.com/viant/parsly v0.3.2-0.20231120160314-11ec855ce00a/go.mod h1:4PKQzioRT9R99ceIhZ6tCD3tp0H0n2dEoIOaLulVvrg=
167
github.com/viant/parsly v0.3.3-0.20240717150634-e1afaedb691b h1:3q166tV28yFdbFV+tXXjH7ViKAmgAgGdoWzMtvhQv28=
178
github.com/viant/parsly v0.3.3-0.20240717150634-e1afaedb691b/go.mod h1:85fneXJbErKMGhSQto3A5ElTQCwl3t74U9cSV0waBHw=
189
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1910
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
20-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2111
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2212
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

index.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package sqlparser
2+
3+
import (
4+
"fmt"
5+
"github.com/viant/parsly"
6+
"github.com/viant/sqlparser/index"
7+
"strings"
8+
)
9+
10+
// ParseDropIndex parses drop table
11+
func ParseDropIndex(SQL string) (*index.Drop, error) {
12+
result := &index.Drop{}
13+
SQL = removeSQLComments(SQL)
14+
cursor := parsly.NewCursor("", []byte(SQL), 0)
15+
err := parseDropIndex(cursor, result)
16+
if err != nil {
17+
return result, fmt.Errorf("%s", SQL)
18+
}
19+
return result, err
20+
}
21+
22+
func parseDropIndex(cursor *parsly.Cursor, dest *index.Drop) error {
23+
match := cursor.MatchAfterOptional(whitespaceMatcher, dropIndexMatcher)
24+
if match.Code != dropIndexToken {
25+
return cursor.NewError(createMatcher)
26+
}
27+
if match = cursor.MatchOne(whitespaceMatcher); match.Code != whitespaceCode {
28+
return cursor.NewError(whitespaceMatcher)
29+
}
30+
if match = cursor.MatchOne(ifExistsMatcher); match.Code == ifExistsToken {
31+
dest.IfExists = true
32+
}
33+
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
34+
if match.Code != identifierCode {
35+
return cursor.NewError(ifNotExistsMatcher)
36+
}
37+
dest.Name = match.Text(cursor)
38+
match = cursor.MatchAfterOptional(whitespaceMatcher, onKeywordMatcher)
39+
if match.Code != onKeyword {
40+
return cursor.NewError(ifNotExistsMatcher)
41+
}
42+
match = cursor.MatchAfterOptional(whitespaceMatcher, selectorMatcher)
43+
if match.Code == selectorTokenCode {
44+
selector := match.Text(cursor)
45+
if index := strings.Index(selector, "."); index != -1 {
46+
dest.Schema = selector[0:index]
47+
dest.Table = selector[index+1:]
48+
} else {
49+
dest.Table = selector
50+
}
51+
}
52+
return nil
53+
}
54+
55+
// ParseCreateIndex parses create table
56+
func ParseCreateIndex(SQL string) (*index.Create, error) {
57+
result := &index.Create{}
58+
SQL = removeSQLComments(SQL)
59+
result.Spec.SQL = SQL
60+
cursor := parsly.NewCursor("", []byte(SQL), 0)
61+
err := parseCreateIndex(cursor, result)
62+
if err != nil {
63+
return result, fmt.Errorf("%s", SQL)
64+
}
65+
return result, err
66+
}
67+
68+
func parseCreateIndex(cursor *parsly.Cursor, dest *index.Create) error {
69+
match := cursor.MatchAfterOptional(whitespaceMatcher, createMatcher)
70+
if match.Code != createToken {
71+
return cursor.NewError(createMatcher)
72+
}
73+
if match = cursor.MatchOne(whitespaceMatcher); match.Code != whitespaceCode {
74+
return cursor.NewError(whitespaceMatcher)
75+
}
76+
77+
if match = cursor.MatchOne(ifNotExistsMatcher); match.Code == ifNotExistsToken {
78+
dest.IfDoesExists = true
79+
}
80+
81+
match = cursor.MatchAfterOptional(whitespaceMatcher, indexMatcher)
82+
if match.Code == indexToken {
83+
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
84+
if match.Code != identifierCode {
85+
return cursor.NewError(ifNotExistsMatcher)
86+
}
87+
dest.Spec.Name = match.Text(cursor)
88+
89+
} else {
90+
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
91+
if match.Code != identifierCode {
92+
return cursor.NewError(ifNotExistsMatcher)
93+
}
94+
dest.Spec.Type = match.Text(cursor)
95+
match = cursor.MatchAfterOptional(whitespaceMatcher, indexMatcher)
96+
if match.Code != indexToken {
97+
return cursor.NewError(indexMatcher)
98+
}
99+
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
100+
if match.Code != identifierCode {
101+
return cursor.NewError(ifNotExistsMatcher)
102+
}
103+
dest.Spec.Name = match.Text(cursor)
104+
}
105+
106+
match = cursor.MatchAfterOptional(whitespaceMatcher, onKeywordMatcher)
107+
if match.Code != onKeyword {
108+
return cursor.NewError(parenthesesMatcher)
109+
}
110+
111+
match = cursor.MatchAfterOptional(whitespaceMatcher, selectorMatcher)
112+
if match.Code != selectorTokenCode {
113+
return cursor.NewError(selectorMatcher)
114+
115+
}
116+
selector := match.Text(cursor)
117+
if index := strings.Index(selector, "."); index != -1 {
118+
dest.Spec.Schema = selector[0:index]
119+
dest.Spec.Table = selector[index+1:]
120+
} else {
121+
dest.Spec.Table = selector
122+
}
123+
124+
match = cursor.MatchAfterOptional(whitespaceMatcher, parenthesesMatcher)
125+
if match.Code != parenthesesCode {
126+
return cursor.NewError(parenthesesMatcher)
127+
}
128+
columnSpec := match.Text(cursor)
129+
pos := cursor.Pos
130+
columnCursor := parsly.NewCursor(cursor.Path, []byte(columnSpec[1:len(columnSpec)-1]), pos)
131+
132+
return parseIndexColumnSpec(dest, match, columnCursor)
133+
}
134+
135+
func parseIndexColumnSpec(dest *index.Create, match *parsly.TokenMatch, cursor *parsly.Cursor) error {
136+
for {
137+
col := &index.ColumnSpec{}
138+
dest.Spec.Columns = append(dest.Spec.Columns, col)
139+
match = cursor.MatchAfterOptional(whitespaceMatcher, identifierMatcher)
140+
if match.Code != identifierCode {
141+
return cursor.NewError(ifNotExistsMatcher)
142+
}
143+
col.Name = match.Text(cursor)
144+
match = cursor.MatchAfterOptional(whitespaceMatcher, nextMatcher)
145+
if match.Code != nextCode {
146+
break
147+
}
148+
}
149+
return nil
150+
}

index/create.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package index
2+
3+
// Create represetns a create
4+
type Create struct {
5+
IfDoesExists bool
6+
Spec
7+
}

index/drop.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package index
2+
3+
// Drop represents a drop
4+
type Drop struct {
5+
IfExists bool
6+
Name string
7+
Schema string
8+
Table string
9+
}

index/spec.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package index
2+
3+
type (
4+
//Spec represents index specification
5+
Spec struct {
6+
Name string
7+
Schema string
8+
Table string
9+
SQL string
10+
Type string
11+
Storage string
12+
Columns []*ColumnSpec
13+
}
14+
15+
ColumnSpec struct {
16+
Name string
17+
Type string
18+
}
19+
)

index_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package sqlparser
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/stretchr/testify/assert"
7+
"testing"
8+
)
9+
10+
func TestParseCreateIndex(t *testing.T) {
11+
12+
{
13+
14+
var testCases = []struct {
15+
description string
16+
SQL string
17+
expect string
18+
}{
19+
20+
{
21+
description: "basc create",
22+
SQL: `CREATE UNIQUE INDEX MyIndex ON schema.table1(COL1, COL2);`,
23+
expect: `CREATE UNIQUE INDEX MyIndex ON schema.table1(COL1, COL2);`,
24+
},
25+
}
26+
for _, testCase := range testCases {
27+
index, err := ParseCreateIndex(testCase.SQL)
28+
if !assert.Nil(t, err) {
29+
fmt.Printf("%v\n", testCase.SQL)
30+
continue
31+
}
32+
actual := Stringify(index)
33+
if !assert.EqualValues(t, testCase.expect, actual) {
34+
data, _ := json.Marshal(index)
35+
fmt.Printf("%s\n", data)
36+
}
37+
}
38+
}
39+
}

kind.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ func ParseKind(SQL string) Kind {
134134
switch secondToken[0] {
135135
case 't': //drop table
136136
return KindDropTable
137+
137138
case 'i':
138139
return KindDropIndex
139140
}
@@ -168,6 +169,10 @@ func ParseKind(SQL string) Kind {
168169
case 'i':
169170
return KindCreateIndex
170171
}
172+
switch thirdToken[0] {
173+
case 'i':
174+
return KindCreateIndex
175+
}
171176
}
172177
return KindUnknown
173178
}

lex.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
parenthesesCode
1717
nextCode
1818
identifierCode
19+
dotCode
1920
starTokenCode
2021
nullTokenCode
2122
notOperator
@@ -59,10 +60,13 @@ const (
5960
keyTokenCode
6061
notNullToken
6162
createTableToken
63+
createToken
64+
indexToken
6265
defaultToken
6366
ifNotExistsToken
6467
ifExistsToken
6568
dropTableToken
69+
dropIndexToken
6670
deleteCode
6771
withKeyword
6872
unionKeyword
@@ -140,6 +144,8 @@ var doubleQuotedStringLiteralMatcher = parsly.NewToken(doubleQuotedStringLiteral
140144
var intLiteralMatcher = parsly.NewToken(intLiteral, `INT`, smatcher.NewIntMatcher())
141145
var numericLiteralMatcher = parsly.NewToken(numericLiteral, `NUMERIC`, matcher.NewNumber())
142146

147+
var dotMatcher = parsly.NewToken(dotCode, ".", matcher.NewByte('.'))
148+
143149
var identifierMatcher = parsly.NewToken(identifierCode, "IDENT", smatcher.NewIdentifier())
144150
var selectorMatcher = parsly.NewToken(selectorTokenCode, "SELECTOR", smatcher.NewSelector(false))
145151
var tableMatcher = parsly.NewToken(tableSelectorTokenCode, "TABLE MATCHER", smatcher.NewSelector(true))
@@ -169,11 +175,20 @@ var ifExistsMatcher = parsly.NewToken(ifExistsToken, "IF EXISTS", matcher.NewSpa
169175
var createTableMatcher = parsly.NewToken(createTableToken, "CREATE TABLE", matcher.NewSpacedSet([]string{
170176
"create table"}, &option.Case{}))
171177

178+
var createMatcher = parsly.NewToken(createToken, "CREATE", matcher.NewSpacedSet([]string{
179+
"create"}, &option.Case{}))
180+
181+
var indexMatcher = parsly.NewToken(indexToken, "INDEX", matcher.NewSpacedSet([]string{
182+
"index"}, &option.Case{}))
183+
172184
var defaultMatcher = parsly.NewToken(defaultToken, "DEFAULT", matcher.NewKeyword("default", &option.Case{}))
173185

174186
var dropTableMatcher = parsly.NewToken(dropTableToken, "DROP TABLE", matcher.NewSpacedSet([]string{
175187
"drop table"}, &option.Case{}))
176188

189+
var dropIndexMatcher = parsly.NewToken(dropIndexToken, "DROP INDEX", matcher.NewSpacedSet([]string{
190+
"drop index"}, &option.Case{}))
191+
177192
var registerKeywordMatcher = parsly.NewToken(registerKeyword, "REGISTER", matcher.NewKeyword("register", &option.Case{}))
178193

179194
var typeKeywordMatcher = parsly.NewToken(typeKeyword, "TYPE", matcher.NewKeyword("type", &option.Case{}))

stringify.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/viant/sqlparser/column"
77
del "github.com/viant/sqlparser/delete"
88
"github.com/viant/sqlparser/expr"
9+
"github.com/viant/sqlparser/index"
910
"github.com/viant/sqlparser/insert"
1011
"github.com/viant/sqlparser/node"
1112
"github.com/viant/sqlparser/query"
@@ -293,6 +294,38 @@ func stringify(n node.Node, builder *bytes.Buffer) {
293294
}
294295
builder.WriteString(")")
295296

297+
case *index.Create:
298+
builder.WriteString("CREATE ")
299+
if actual.Type != "" {
300+
builder.WriteString(actual.Type)
301+
builder.WriteString(" ")
302+
}
303+
304+
builder.WriteString("INDEX ")
305+
306+
if actual.IfDoesExists {
307+
builder.WriteString("IF NOT EXISTS ")
308+
}
309+
builder.WriteString(actual.Name)
310+
builder.WriteString(" ON ")
311+
312+
if actual.Schema != "" {
313+
builder.WriteString(actual.Schema)
314+
builder.WriteString(".")
315+
builder.WriteString(actual.Table)
316+
} else {
317+
builder.WriteString(actual.Table)
318+
}
319+
320+
builder.WriteString("(")
321+
for i, col := range actual.Columns {
322+
if i > 0 {
323+
builder.WriteString(", ")
324+
}
325+
builder.WriteString(col.Name)
326+
}
327+
builder.WriteString(");")
328+
296329
case *column.Spec:
297330
builder.WriteString(actual.Name)
298331
builder.WriteString(" ")

0 commit comments

Comments
 (0)