Parse tables and @things better

This commit is contained in:
Tobie Morgan Hitchcock 2016-09-14 22:22:18 +01:00
parent 974f75eea0
commit 1fc814bb43
4 changed files with 229 additions and 84 deletions

View file

@ -18,9 +18,47 @@ import (
"regexp" "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) { func (p *parser) parseName() (string, error) {
_, lit, err := p.shouldBe(IDENT, NUMBER, DOUBLE)
if err != nil { if err != nil {
return string(""), &ParseError{Found: lit, Expected: []string{"name"}} return string(""), &ParseError{Found: lit, Expected: []string{"name"}}
} }

View file

@ -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{}) { 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) { if s.p.is(tok, REGION) {
return PARAM, lit, nil 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 { for {
if ch := s.next(); ch == eof { if ch := s.next(); ch == eof {
break break
} else if isLetter(ch) { } else if isThingChar(ch) {
tok, lit, _ = s.scanIdent(ch) tok, lit, _ = s.scanIdent(ch)
beg.WriteString(lit) beg.WriteString(lit)
} else if isNumber(ch) { break
tok, lit, _ = s.scanNumber(ch)
beg.WriteString(lit)
} else if ch == '`' { } else if ch == '`' {
tok, lit, _ = s.scanQuoted(ch) tok, lit, _ = s.scanQuoted(ch)
beg.WriteString(lit) beg.WriteString(lit)
break
} else if ch == '{' { } else if ch == '{' {
tok, lit, _ = s.scanQuoted(ch) tok, lit, _ = s.scanQuoted(ch)
beg.WriteString(lit) beg.WriteString(lit)
break
} else if ch == '⟨' { } else if ch == '⟨' {
tok, lit, _ = s.scanQuoted(ch) tok, lit, _ = s.scanQuoted(ch)
beg.WriteString(lit) beg.WriteString(lit)
break
} else { } else {
s.undo() s.undo()
break break
} }
} }
if beg.Len() < 1 { if beg.Len() < 1 || tok == ILLEGAL {
return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val 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 { for {
if ch := s.next(); ch == eof { if ch := s.next(); ch == eof {
break break
} else if isLetter(ch) { } else if isThingChar(ch) {
tok, lit, _ = s.scanIdent(ch) tok, lit, _ = s.scanIdent(ch)
end.WriteString(lit) end.WriteString(lit)
} else if isNumber(ch) { break
tok, lit, _ = s.scanNumber(ch)
end.WriteString(lit)
} else if ch == '`' { } else if ch == '`' {
tok, lit, _ = s.scanQuoted(ch) tok, lit, _ = s.scanQuoted(ch)
beg.WriteString(lit) end.WriteString(lit)
break
} else if ch == '{' { } else if ch == '{' {
tok, lit, _ = s.scanQuoted(ch) tok, lit, _ = s.scanQuoted(ch)
end.WriteString(lit) end.WriteString(lit)
break
} else if ch == '⟨' { } else if ch == '⟨' {
tok, lit, _ = s.scanQuoted(ch) tok, lit, _ = s.scanQuoted(ch)
end.WriteString(lit) end.WriteString(lit)
break
} else { } else {
s.undo() s.undo()
break break
} }
} }
if end.Len() < 1 { if end.Len() < 1 || tok == ILLEGAL {
return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val
} }
val = &Thing{ val = NewThing(beg.String(), end.String())
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
}
// Otherwise return as a regular thing. // Otherwise return as a regular thing.
return THING, buf.String() + beg.String() + mid.String() + end.String(), val 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{}) { func (s *scanner) scanString(chp ...rune) (tok Token, lit string, val interface{}) {
beg := chp[0] beg := chp[0]
@ -760,5 +760,10 @@ func isIdentChar(ch rune) bool {
return isLetter(ch) || isNumber(ch) || ch == '.' || ch == '_' || ch == '*' 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. // eof represents a marker rune for the end of the reader.
var eof = rune(0) var eof = rune(0)

View file

@ -270,6 +270,9 @@ func Test_Parse_Queries_Explain(t *testing.T) {
func Test_Parse_Queries_Select(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{ var tests = []tester{
{ {
sql: `SELECT`, sql: `SELECT`,
@ -325,6 +328,41 @@ func Test_Parse_Queries_Select(t *testing.T) {
sql: `SELECT * FROM person:uuid`, sql: `SELECT * FROM person:uuid`,
err: "Found `:` but expected `EOF, ;`", 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`, sql: `SELECT * FROM person`,
res: &Query{Statements: []Statement{&SelectStatement{ res: &Query{Statements: []Statement{&SelectStatement{
@ -339,6 +377,13 @@ func Test_Parse_Queries_Select(t *testing.T) {
What: []Expr{&Table{"person"}, &Table{"tweet"}}, 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`, sql: `SELECT * FROM @person:1a`,
res: &Query{Statements: []Statement{&SelectStatement{ res: &Query{Statements: []Statement{&SelectStatement{
@ -346,6 +391,20 @@ func Test_Parse_Queries_Select(t *testing.T) {
What: []Expr{&Thing{TB: "person", ID: "1a"}}, 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`, sql: `SELECT * FROM @person:123456`,
res: &Query{Statements: []Statement{&SelectStatement{ 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)}}, 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`, sql: `SELECT * FROM @person:123.456`,
res: &Query{Statements: []Statement{&SelectStatement{ 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)}}, 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`, sql: `SELECT * FROM @person:123.456.789.012`,
res: &Query{Statements: []Statement{&SelectStatement{ 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"}}, 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⟩`, sql: `SELECT * FROM @person:⟨A250C5A3-948F-4657-88AD-FF5F27B5B24E⟩`,
res: &Query{Statements: []Statement{&SelectStatement{ 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"}}, 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⟩`, sql: `SELECT * FROM @⟨email addresses⟩:⟨this\qis\nodd⟩`,
err: "Found `@email addresses:thisqis\nodd` but expected `table name or record id`", 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`, sql: `DEFINE TABLE`,
err: "Found `` but expected `name`", 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`, sql: `DEFINE TABLE person`,
res: &Query{Statements: []Statement{&DefineTableStatement{ res: &Query{Statements: []Statement{&DefineTableStatement{
@ -1729,12 +1860,28 @@ func Test_Parse_Queries_Remove(t *testing.T) {
sql: `REMOVE TABLE`, sql: `REMOVE TABLE`,
err: "Found `` but expected `name`", 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`, sql: `REMOVE TABLE person`,
res: &Query{Statements: []Statement{&RemoveTableStatement{ res: &Query{Statements: []Statement{&RemoveTableStatement{
What: []string{"person"}, What: []string{"person"},
}}}, }}},
}, },
{
sql: `REMOVE TABLE person something`,
err: "Found `something` but expected `EOF, ;`",
},
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
{ {
sql: `REMOVE RULES`, sql: `REMOVE RULES`,

View file

@ -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
}