From 1fc814bb435f096b8a3b8dbb05e7d560f4d40194 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Wed, 14 Sep 2016 22:22:18 +0100 Subject: [PATCH] Parse tables and @things better --- sql/exprs.go | 40 ++++++++++++- sql/scanner.go | 81 +++++++++++++------------- sql/sql_test.go | 147 ++++++++++++++++++++++++++++++++++++++++++++++++ sql/what.go | 45 --------------- 4 files changed, 229 insertions(+), 84 deletions(-) delete mode 100644 sql/what.go diff --git a/sql/exprs.go b/sql/exprs.go index 5e17b9b0..dce2a118 100644 --- a/sql/exprs.go +++ b/sql/exprs.go @@ -18,9 +18,47 @@ import ( "regexp" ) +func (p *parser) parseWhat() (mul []Expr, err error) { + + for { + + tok, lit, err := p.shouldBe(IDENT, NUMBER, DOUBLE, THING, PARAM) + if err != nil { + return nil, &ParseError{Found: lit, Expected: []string{"table name or record id"}} + } + + if p.is(tok, IDENT, NUMBER, DOUBLE) { + one, _ := p.declare(TABLE, lit) + mul = append(mul, one) + } + + if p.is(tok, THING) { + one, _ := p.declare(THING, lit) + mul = append(mul, one) + } + + if p.is(tok, PARAM) { + one, err := p.declare(PARAM, lit) + if err != nil { + return nil, err + } + mul = append(mul, one) + } + + // If the next token is not a comma then break the loop. + if _, _, exi := p.mightBe(COMMA); !exi { + break + } + + } + + return + +} - _, lit, err := p.shouldBe(IDENT, NUMBER, DOUBLE, DATE, TIME) func (p *parser) parseName() (string, error) { + + _, lit, err := p.shouldBe(IDENT, NUMBER, DOUBLE) if err != nil { return string(""), &ParseError{Found: lit, Expected: []string{"name"}} } diff --git a/sql/scanner.go b/sql/scanner.go index 942aa8d7..ffd8a0ca 100644 --- a/sql/scanner.go +++ b/sql/scanner.go @@ -325,13 +325,33 @@ func (s *scanner) scanCommentMultiple(chp ...rune) (tok Token, lit string, val i func (s *scanner) scanParams(chp ...rune) (tok Token, lit string, val interface{}) { - tok, lit, val = s.scanIdent(chp...) + tok, lit, _ = s.scanIdent() - if s.p.is(tok, IDENT) { - return PARAM, lit, nil + if s.p.is(tok, REGION) { + return ILLEGAL, lit, val } - return + if s.p.is(tok, ILLEGAL) { + return ILLEGAL, lit, val + } + + return PARAM, lit, val + +} + +func (s *scanner) scanQuoted(chp ...rune) (tok Token, lit string, val interface{}) { + + tok, lit, _ = s.scanString(chp...) + + if s.p.is(tok, REGION) { + return ILLEGAL, lit, val + } + + if s.p.is(tok, ILLEGAL) { + return ILLEGAL, lit, val + } + + return IDENT, lit, val } @@ -393,28 +413,29 @@ func (s *scanner) scanThing(chp ...rune) (tok Token, lit string, val interface{} for { if ch := s.next(); ch == eof { break - } else if isLetter(ch) { + } else if isThingChar(ch) { tok, lit, _ = s.scanIdent(ch) beg.WriteString(lit) - } else if isNumber(ch) { - tok, lit, _ = s.scanNumber(ch) - beg.WriteString(lit) + break } else if ch == '`' { tok, lit, _ = s.scanQuoted(ch) beg.WriteString(lit) + break } else if ch == '{' { tok, lit, _ = s.scanQuoted(ch) beg.WriteString(lit) + break } else if ch == '⟨' { tok, lit, _ = s.scanQuoted(ch) beg.WriteString(lit) + break } else { s.undo() break } } - if beg.Len() < 1 { + if beg.Len() < 1 || tok == ILLEGAL { return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val } @@ -435,42 +456,33 @@ func (s *scanner) scanThing(chp ...rune) (tok Token, lit string, val interface{} for { if ch := s.next(); ch == eof { break - } else if isLetter(ch) { + } else if isThingChar(ch) { tok, lit, _ = s.scanIdent(ch) end.WriteString(lit) - } else if isNumber(ch) { - tok, lit, _ = s.scanNumber(ch) - end.WriteString(lit) + break } else if ch == '`' { tok, lit, _ = s.scanQuoted(ch) - beg.WriteString(lit) + end.WriteString(lit) + break } else if ch == '{' { tok, lit, _ = s.scanQuoted(ch) end.WriteString(lit) + break } else if ch == '⟨' { tok, lit, _ = s.scanQuoted(ch) end.WriteString(lit) + break } else { s.undo() break } } - if end.Len() < 1 { + if end.Len() < 1 || tok == ILLEGAL { return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val } - val = &Thing{ - TB: beg.String(), - ID: end.String(), - } - - switch tok { - case DATE, TIME, NUMBER, DOUBLE: - val.(*Thing).ID, _ = s.p.declare(tok, end.String()) - case REGION: - return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val - } + val = NewThing(beg.String(), end.String()) // Otherwise return as a regular thing. return THING, buf.String() + beg.String() + mid.String() + end.String(), val @@ -516,18 +528,6 @@ func (s *scanner) scanNumber(chp ...rune) (tok Token, lit string, val interface{ } -func (s *Scanner) scanQuoted(chp ...rune) (tok Token, lit string, val interface{}) { - - tok, lit, val = s.scanString(chp...) - - if s.p.is(tok, STRING) { - return IDENT, lit, nil - } - - return - -} - func (s *scanner) scanString(chp ...rune) (tok Token, lit string, val interface{}) { beg := chp[0] @@ -760,5 +760,10 @@ func isIdentChar(ch rune) bool { return isLetter(ch) || isNumber(ch) || ch == '.' || ch == '_' || ch == '*' } +// isThingChar returns true if the rune is allowed in a THING. +func isThingChar(ch rune) bool { + return isLetter(ch) || isNumber(ch) || ch == '_' +} + // eof represents a marker rune for the end of the reader. var eof = rune(0) diff --git a/sql/sql_test.go b/sql/sql_test.go index f20de5e1..6eb56072 100644 --- a/sql/sql_test.go +++ b/sql/sql_test.go @@ -270,6 +270,9 @@ func Test_Parse_Queries_Explain(t *testing.T) { func Test_Parse_Queries_Select(t *testing.T) { + date, _ := time.Parse("2006-01-02", "1987-06-22") + nano, _ := time.Parse(time.RFC3339, "1987-06-22T08:30:30.511Z") + var tests = []tester{ { sql: `SELECT`, @@ -325,6 +328,41 @@ func Test_Parse_Queries_Select(t *testing.T) { sql: `SELECT * FROM person:uuid`, err: "Found `:` but expected `EOF, ;`", }, + { + sql: "SELECT * FROM 111", + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"111"}}, + }}}, + }, + { + sql: "SELECT * FROM `111`", + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"111"}}, + }}}, + }, + { + sql: "SELECT * FROM `2006-01-02`", + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"2006-01-02"}}, + }}}, + }, + { + sql: "SELECT * FROM `2006-01-02T15:04:05+07:00`", + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"2006-01-02T15:04:05+07:00"}}, + }}}, + }, + { + sql: "SELECT * FROM `2006-01-02T15:04:05.999999999+07:00`", + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"2006-01-02T15:04:05.999999999+07:00"}}, + }}}, + }, { sql: `SELECT * FROM person`, res: &Query{Statements: []Statement{&SelectStatement{ @@ -339,6 +377,13 @@ func Test_Parse_Queries_Select(t *testing.T) { What: []Expr{&Table{"person"}, &Table{"tweet"}}, }}}, }, + { + sql: `SELECT * FROM @111:1a`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "111", ID: "1a"}}, + }}}, + }, { sql: `SELECT * FROM @person:1a`, res: &Query{Statements: []Statement{&SelectStatement{ @@ -346,6 +391,20 @@ func Test_Parse_Queries_Select(t *testing.T) { What: []Expr{&Thing{TB: "person", ID: "1a"}}, }}}, }, + { + sql: `SELECT * FROM @person:⟨1a⟩`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: "1a"}}, + }}}, + }, + { + sql: `SELECT * FROM @person:{1a}`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: "1a"}}, + }}}, + }, { sql: `SELECT * FROM @person:123456`, res: &Query{Statements: []Statement{&SelectStatement{ @@ -353,6 +412,20 @@ func Test_Parse_Queries_Select(t *testing.T) { What: []Expr{&Thing{TB: "person", ID: float64(123456)}}, }}}, }, + { + sql: `SELECT * FROM @person:⟨123456⟩`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: float64(123456)}}, + }}}, + }, + { + sql: `SELECT * FROM @person:{123456}`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: float64(123456)}}, + }}}, + }, { sql: `SELECT * FROM @person:123.456`, res: &Query{Statements: []Statement{&SelectStatement{ @@ -360,6 +433,20 @@ func Test_Parse_Queries_Select(t *testing.T) { What: []Expr{&Thing{TB: "person", ID: float64(123.456)}}, }}}, }, + { + sql: `SELECT * FROM @person:⟨123.456⟩`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: float64(123.456)}}, + }}}, + }, + { + sql: `SELECT * FROM @person:{123.456}`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: float64(123.456)}}, + }}}, + }, { sql: `SELECT * FROM @person:123.456.789.012`, res: &Query{Statements: []Statement{&SelectStatement{ @@ -381,6 +468,34 @@ func Test_Parse_Queries_Select(t *testing.T) { What: []Expr{&Thing{TB: "person", ID: "123.456.789.012"}}, }}}, }, + { + sql: `SELECT * FROM @person:⟨1987-06-22⟩`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: date}}, + }}}, + }, + { + sql: `SELECT * FROM @person:{1987-06-22}`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: date}}, + }}}, + }, + { + sql: `SELECT * FROM @person:⟨1987-06-22T08:30:30.511Z⟩`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: nano}}, + }}}, + }, + { + sql: `SELECT * FROM @person:{1987-06-22T08:30:30.511Z}`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: nano}}, + }}}, + }, { sql: `SELECT * FROM @person:⟨A250C5A3-948F-4657-88AD-FF5F27B5B24E⟩`, res: &Query{Statements: []Statement{&SelectStatement{ @@ -451,6 +566,10 @@ func Test_Parse_Queries_Select(t *testing.T) { What: []Expr{&Thing{TB: "email addresses", ID: "tobie+spam@abcum.com"}}, }}}, }, + { + sql: `SELECT * FROM @{person}test:id`, + err: "Found `@person` but expected `table name or record id`", + }, { sql: `SELECT * FROM @⟨email addresses⟩:⟨this\qis\nodd⟩`, err: "Found `@email addresses:thisqis\nodd` but expected `table name or record id`", @@ -1201,6 +1320,18 @@ func Test_Parse_Queries_Define(t *testing.T) { sql: `DEFINE TABLE`, err: "Found `` but expected `name`", }, + { + sql: `DEFINE TABLE 111`, + res: &Query{Statements: []Statement{&DefineTableStatement{ + What: []string{"111"}, + }}}, + }, + { + sql: `DEFINE TABLE 111.111`, + res: &Query{Statements: []Statement{&DefineTableStatement{ + What: []string{"111.111"}, + }}}, + }, { sql: `DEFINE TABLE person`, res: &Query{Statements: []Statement{&DefineTableStatement{ @@ -1729,12 +1860,28 @@ func Test_Parse_Queries_Remove(t *testing.T) { sql: `REMOVE TABLE`, err: "Found `` but expected `name`", }, + { + sql: `REMOVE TABLE 111`, + res: &Query{Statements: []Statement{&RemoveTableStatement{ + What: []string{"111"}, + }}}, + }, + { + sql: `REMOVE TABLE 111.111`, + res: &Query{Statements: []Statement{&RemoveTableStatement{ + What: []string{"111.111"}, + }}}, + }, { sql: `REMOVE TABLE person`, res: &Query{Statements: []Statement{&RemoveTableStatement{ What: []string{"person"}, }}}, }, + { + sql: `REMOVE TABLE person something`, + err: "Found `something` but expected `EOF, ;`", + }, // ---------------------------------------------------------------------- { sql: `REMOVE RULES`, diff --git a/sql/what.go b/sql/what.go deleted file mode 100644 index d4ba7f80..00000000 --- a/sql/what.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2016 Abcum Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package sql - -func (p *Parser) parseWhat() (mul []Expr, err error) { - - for { - - tok, lit, err := p.shouldBe(IDENT, THING) - if err != nil { - return nil, &ParseError{Found: lit, Expected: []string{"table name or record id"}} - } - - if p.is(tok, IDENT) { - one, _ := p.declare(TABLE, lit) - mul = append(mul, one) - } - - if p.is(tok, THING) { - one, _ := p.declare(THING, lit) - mul = append(mul, one) - } - - // If the next token is not a comma then break the loop. - if _, _, exi := p.mightBe(COMMA); !exi { - break - } - - } - - return - -}