Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Commit b820870

Browse files
Parse CREATE VIEW statements
Signed-off-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
1 parent 68b087c commit b820870

File tree

5 files changed

+364
-5
lines changed

5 files changed

+364
-5
lines changed

sql/parse/parse.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
var (
3737
describeTablesRegex = regexp.MustCompile(`^(describe|desc)\s+table\s+(.*)`)
3838
createIndexRegex = regexp.MustCompile(`^create\s+index\s+`)
39+
createViewRegex = regexp.MustCompile(`^create\s+(or\s+replace\s+)?view\s+`)
3940
dropIndexRegex = regexp.MustCompile(`^drop\s+index\s+`)
4041
showIndexRegex = regexp.MustCompile(`^show\s+(index|indexes|keys)\s+(from|in)\s+\S+\s*`)
4142
showCreateRegex = regexp.MustCompile(`^show create\s+\S+\s*`)
@@ -47,7 +48,6 @@ var (
4748
unlockTablesRegex = regexp.MustCompile(`^unlock\s+tables$`)
4849
lockTablesRegex = regexp.MustCompile(`^lock\s+tables\s`)
4950
setRegex = regexp.MustCompile(`^set\s+`)
50-
createViewRegex = regexp.MustCompile(`^create\s+view\s+`)
5151
)
5252

5353
// These constants aren't exported from vitess for some reason. This could be removed if we changed this.
@@ -82,6 +82,8 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) {
8282
return parseDescribeTables(lowerQuery)
8383
case createIndexRegex.MatchString(lowerQuery):
8484
return parseCreateIndex(ctx, s)
85+
case createViewRegex.MatchString(lowerQuery):
86+
return parseCreateView(ctx, s)
8587
case dropIndexRegex.MatchString(lowerQuery):
8688
return parseDropIndex(s)
8789
case showIndexRegex.MatchString(lowerQuery):
@@ -104,9 +106,6 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) {
104106
return parseLockTables(ctx, s)
105107
case setRegex.MatchString(lowerQuery):
106108
s = fixSetQuery(s)
107-
case createViewRegex.MatchString(lowerQuery):
108-
// CREATE VIEW parses as a CREATE DDL statement with an empty table spec
109-
return nil, ErrUnsupportedFeature.New("CREATE VIEW")
110109
}
111110

112111
stmt, err := sqlparser.Parse(s)

sql/parse/parse_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1296,7 +1296,6 @@ var fixturesErrors = map[string]*errors.Kind{
12961296
`SELECT INTERVAL 1 DAY + INTERVAL 1 DAY`: ErrUnsupportedSyntax,
12971297
`SELECT '2018-05-01' + (INTERVAL 1 DAY + INTERVAL 1 DAY)`: ErrUnsupportedSyntax,
12981298
`SELECT AVG(DISTINCT foo) FROM b`: ErrUnsupportedSyntax,
1299-
`CREATE VIEW view1 AS SELECT x FROM t1 WHERE x>0`: ErrUnsupportedFeature,
13001299
}
13011300

13021301
func TestParseErrors(t *testing.T) {

sql/parse/util.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,113 @@ func expectQuote(r *bufio.Reader) error {
308308

309309
return nil
310310
}
311+
312+
func maybe(matched *bool, str string) parseFunc {
313+
return func (rd *bufio.Reader) error {
314+
*matched = false
315+
strLength := len(str)
316+
317+
data, err := rd.Peek(strLength)
318+
if err != nil {
319+
// If there are not enough runes, what we expected was not there, which
320+
// is not an error per se.
321+
if len(data) < strLength {
322+
return nil
323+
}
324+
325+
return err
326+
}
327+
328+
if strings.ToLower(string(data)) == str {
329+
_, err := rd.Discard(strLength)
330+
if err != nil {
331+
return err
332+
}
333+
334+
*matched = true
335+
return nil
336+
}
337+
338+
return nil
339+
}
340+
}
341+
342+
func multiMaybe(matched *bool, strings ...string) parseFunc {
343+
return func (rd *bufio.Reader) error {
344+
*matched = false
345+
first := true
346+
for _, str := range strings {
347+
if err := maybe(matched, str)(rd); err != nil {
348+
return err
349+
}
350+
351+
if !*matched {
352+
if first {
353+
return nil
354+
}
355+
356+
// TODO: add actual string parsed
357+
return errUnexpectedSyntax.New(str, "smth else")
358+
}
359+
360+
first = false
361+
362+
if err := skipSpaces(rd); err != nil {
363+
return err
364+
}
365+
}
366+
*matched = true
367+
return nil
368+
}
369+
}
370+
371+
// Read a list of strings separated by the specified separator, with a rune
372+
// indicating the opening of the list and another one specifying its closing.
373+
// For example, readList('(', ',', ')', list) parses "(uno, dos,tres)" and
374+
// populates list with the array of strings ["uno", "dos", "tres"]
375+
// If the opening is not found, do not advance the reader
376+
func maybeList(opening, separator, closing rune, list []string) parseFunc {
377+
return func(rd *bufio.Reader) error {
378+
r, _, err := rd.ReadRune()
379+
if err != nil {
380+
return err
381+
}
382+
383+
if r != opening {
384+
rd.UnreadRune()
385+
return nil
386+
}
387+
388+
for {
389+
var newItem string
390+
err := parseFuncs{
391+
skipSpaces,
392+
readIdent(&newItem),
393+
skipSpaces,
394+
}.exec(rd)
395+
396+
if err != nil {
397+
return err
398+
}
399+
400+
r, _, err := rd.ReadRune()
401+
if err != nil {
402+
return err
403+
}
404+
405+
switch r {
406+
case closing:
407+
list = append(list, newItem)
408+
return nil
409+
case separator:
410+
list = append(list, newItem)
411+
continue
412+
default:
413+
return errUnexpectedSyntax.New(
414+
fmt.Sprintf("%v or %v", separator, closing),
415+
string(r),
416+
)
417+
}
418+
}
419+
}
420+
}

sql/parse/views.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package parse
2+
3+
import (
4+
"bufio"
5+
"strings"
6+
// "io"
7+
8+
"github.com/src-d/go-mysql-server/sql"
9+
"github.com/src-d/go-mysql-server/sql/plan"
10+
11+
"gopkg.in/src-d/go-errors.v1"
12+
"vitess.io/vitess/go/vt/sqlparser"
13+
)
14+
15+
var ErrMalformedCreateView = errors.NewKind("view definition %#v is not a SELECT query")
16+
17+
// Parses
18+
// CREATE [OR REPLACE] VIEW view_name [(col1, col2, ...)] AS select_statement
19+
// and returns a NewCreateView node in case of success
20+
func parseCreateView(ctx *sql.Context, s string) (sql.Node, error) {
21+
r := bufio.NewReader(strings.NewReader(s))
22+
23+
24+
var (
25+
viewName, subquery string
26+
columns []string
27+
isReplace bool
28+
)
29+
30+
err := parseFuncs{
31+
expect("create"),
32+
skipSpaces,
33+
multiMaybe(&isReplace, "or", "replace"),
34+
skipSpaces,
35+
expect("view"),
36+
skipSpaces,
37+
readIdent(&viewName),
38+
skipSpaces,
39+
maybeList('(', ',', ')', columns),
40+
skipSpaces,
41+
expect("as"),
42+
skipSpaces,
43+
readRemaining(&subquery),
44+
checkEOF,
45+
}.exec(r)
46+
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
subqueryStatement, err := sqlparser.Parse(subquery)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
selectStatement, ok := subqueryStatement.(*sqlparser.Select)
57+
if !ok {
58+
return nil, ErrMalformedCreateView.New(subqueryStatement)
59+
}
60+
61+
subqueryNode, err := convertSelect(ctx, selectStatement)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
subqueryAlias := plan.NewSubqueryAlias(viewName, subqueryNode)
67+
68+
return plan.NewCreateView(
69+
sql.UnresolvedDatabase(""), viewName, columns, subqueryAlias,
70+
), nil
71+
}

0 commit comments

Comments
 (0)