Add subqueries, subexpressions, and paths to SQL

This commit is contained in:
Tobie Morgan Hitchcock 2016-11-04 09:57:17 +00:00
parent 28d1806785
commit 68c22bc25c
17 changed files with 760 additions and 227 deletions

View file

@ -21,11 +21,11 @@ import (
func (e *executor) executeLetStatement(txn kvs.TX, ast *sql.LetStatement) (out []interface{}, err error) { func (e *executor) executeLetStatement(txn kvs.TX, ast *sql.LetStatement) (out []interface{}, err error) {
switch expr := ast.Expr.(type) { switch what := ast.What.(type) {
default: default:
e.Set(ast.Name, expr) e.Set(ast.Name, what)
case *sql.Param: case *sql.Param:
e.Set(ast.Name, e.Get(expr.ID)) e.Set(ast.Name, e.Get(what.ID))
} }
return return

View file

@ -21,15 +21,11 @@ import (
func (e *executor) executeReturnStatement(txn kvs.TX, ast *sql.ReturnStatement) (out []interface{}, err error) { func (e *executor) executeReturnStatement(txn kvs.TX, ast *sql.ReturnStatement) (out []interface{}, err error) {
for _, w := range ast.What { switch what := ast.What.(type) {
default:
switch what := w.(type) { out = append(out, what)
default: case *sql.Param:
out = append(out, what) out = append(out, e.ctx.Get(what.ID).Data())
case *sql.Param:
out = append(out, e.ctx.Get(what.ID).Data())
}
} }
return return

View file

@ -48,11 +48,6 @@ type CancelStatement struct{}
// UseStatement represents a SQL COMMIT TRANSACTION statement. // UseStatement represents a SQL COMMIT TRANSACTION statement.
type CommitStatement struct{} type CommitStatement struct{}
// ReturnStatement represents a SQL RETURN statement.
type ReturnStatement struct {
What []Expr
}
// -------------------------------------------------- // --------------------------------------------------
// Use // Use
// -------------------------------------------------- // --------------------------------------------------
@ -81,11 +76,13 @@ type InfoStatement struct {
// LetStatement represents a SQL LET statement. // LetStatement represents a SQL LET statement.
type LetStatement struct { type LetStatement struct {
KV string `cork:"-" codec:"-"`
NS string `cork:"-" codec:"-"`
DB string `cork:"-" codec:"-"`
Name string `cork:"-" codec:"-"` Name string `cork:"-" codec:"-"`
Expr Expr `cork:"-" codec:"-"` What Expr `cork:"-" codec:"-"`
}
// ReturnStatement represents a SQL RETURN statement.
type ReturnStatement struct {
What Expr `cork:"-" codec:"-"`
} }
// -------------------------------------------------- // --------------------------------------------------
@ -99,7 +96,7 @@ type LiveStatement struct {
DB string `cork:"-" codec:"-"` DB string `cork:"-" codec:"-"`
Expr []*Field `cork:"expr" codec:"expr"` Expr []*Field `cork:"expr" codec:"expr"`
What []Expr `cork:"what" codec:"what"` What []Expr `cork:"what" codec:"what"`
Cond []Expr `cork:"cond" codec:"cond"` Cond Expr `cork:"cond" codec:"cond"`
Echo Token `cork:"echo" codec:"echo"` Echo Token `cork:"echo" codec:"echo"`
} }
@ -110,7 +107,7 @@ type SelectStatement struct {
DB string `cork:"-" codec:"-"` DB string `cork:"-" codec:"-"`
Expr []*Field `cork:"expr" codec:"expr"` Expr []*Field `cork:"expr" codec:"expr"`
What []Expr `cork:"what" codec:"what"` What []Expr `cork:"what" codec:"what"`
Cond []Expr `cork:"cond" codec:"cond"` Cond Expr `cork:"cond" codec:"cond"`
Group []*Group `cork:"group" codec:"group"` Group []*Group `cork:"group" codec:"group"`
Order []*Order `cork:"order" codec:"order"` Order []*Order `cork:"order" codec:"order"`
Limit Expr `cork:"limit" codec:"limit"` Limit Expr `cork:"limit" codec:"limit"`
@ -136,7 +133,7 @@ type UpdateStatement struct {
DB string `cork:"-" codec:"-"` DB string `cork:"-" codec:"-"`
What []Expr `cork:"what" codec:"what"` What []Expr `cork:"what" codec:"what"`
Data []Expr `cork:"data" codec:"data"` Data []Expr `cork:"data" codec:"data"`
Cond []Expr `cork:"cond" codec:"cond"` Cond Expr `cork:"cond" codec:"cond"`
Echo Token `cork:"echo" codec:"echo"` Echo Token `cork:"echo" codec:"echo"`
} }
@ -147,7 +144,7 @@ type DeleteStatement struct {
DB string `cork:"-" codec:"-"` DB string `cork:"-" codec:"-"`
Hard bool `cork:"hard" codec:"hard"` Hard bool `cork:"hard" codec:"hard"`
What []Expr `cork:"what" codec:"what"` What []Expr `cork:"what" codec:"what"`
Cond []Expr `cork:"cond" codec:"cond"` Cond Expr `cork:"cond" codec:"cond"`
Echo Token `cork:"echo" codec:"echo"` Echo Token `cork:"echo" codec:"echo"`
} }
@ -156,10 +153,11 @@ type RelateStatement struct {
KV string `cork:"-" codec:"-"` KV string `cork:"-" codec:"-"`
NS string `cork:"-" codec:"-"` NS string `cork:"-" codec:"-"`
DB string `cork:"-" codec:"-"` DB string `cork:"-" codec:"-"`
Type []Expr `cork:"type" codec:"type"` Type Expr `cork:"type" codec:"type"`
From []Expr `cork:"from" codec:"from"` From []Expr `cork:"from" codec:"from"`
To []Expr `cork:"to" codec:"to"` With []Expr `cork:"with" codec:"with"`
Data []Expr `cork:"data" codec:"data"` Data []Expr `cork:"data" codec:"data"`
Uniq bool `cork:"uniq" codec:"uniq"`
Echo Token `cork:"echo" codec:"echo"` Echo Token `cork:"echo" codec:"echo"`
} }
@ -218,7 +216,7 @@ type DefineRulesStatement struct {
What []string `cork:"-" codec:"-"` What []string `cork:"-" codec:"-"`
When []string `cork:"-" codec:"-"` When []string `cork:"-" codec:"-"`
Rule string `cork:"rule" codec:"rule"` Rule string `cork:"rule" codec:"rule"`
Cond []Expr `cork:"cond" codec:"cond"` Cond Expr `cork:"cond" codec:"cond"`
} }
// RemoveRulesStatement represents an SQL REMOVE RULES statement. // RemoveRulesStatement represents an SQL REMOVE RULES statement.
@ -299,7 +297,7 @@ type DefineViewStatement struct {
Name string `cork:"name" codec:"name"` Name string `cork:"name" codec:"name"`
Expr []*Field `cork:"expr" codec:"expr"` Expr []*Field `cork:"expr" codec:"expr"`
What []Expr `cork:"what" codec:"what"` What []Expr `cork:"what" codec:"what"`
Cond []Expr `cork:"cond" codec:"cond"` Cond Expr `cork:"cond" codec:"cond"`
Group []*Group `cork:"group" codec:"group"` Group []*Group `cork:"group" codec:"group"`
} }
@ -318,9 +316,12 @@ type RemoveViewStatement struct {
// Expr represents a sql expression // Expr represents a sql expression
type Expr interface{} type Expr interface{}
// All represents a wildcard expression. // All represents a * expression.
type All struct{} type All struct{}
// Any represents a ? expression.
type Any struct{}
// Asc represents the ASC expression. // Asc represents the ASC expression.
type Asc struct{} type Asc struct{}
@ -373,19 +374,58 @@ type Order struct {
Dir Expr Dir Expr
} }
// ClosedExpression represents a parenthesized expression. // --------------------------------------------------
type ClosedExpression struct { // Expressions
// --------------------------------------------------
// SubExpression represents a subquery.
type SubExpression struct {
Expr Expr Expr Expr
} }
// BinaryExpression represents a binary expression tree, // FuncExpression represents a function call.
type FuncExpression struct {
Name string
Args []Expr
}
// DataExpression represents a SET expression.
type DataExpression struct {
LHS Expr
Op Token
RHS Expr
}
// BinaryExpression represents a WHERE expression.
type BinaryExpression struct { type BinaryExpression struct {
LHS Expr LHS Expr
Op Token Op Token
RHS Expr RHS Expr
} }
// DiffExpression represents a JSON DIFF PATCH // PathExpression represents a path expression.
type PathExpression struct {
Expr []Expr
}
// PartExpression represents a path part expression.
type PartExpression struct {
Part Expr
}
// PartExpression represents a path join expression.
type JoinExpression struct {
Join Token
}
// SubpExpression represents a sub path expression.
type SubpExpression struct {
What []Expr
Name string
Cond Expr
}
// DiffExpression represents a JSON to DIFF
type DiffExpression struct { type DiffExpression struct {
JSON interface{} JSON interface{}
} }

View file

@ -1,93 +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) parseCond() (mul []Expr, err error) {
var tok Token
var lit string
// Remove the WHERE keyword
if _, _, exi := p.mightBe(WHERE); !exi {
return nil, nil
}
for {
one := &BinaryExpression{}
tok, lit, err = p.shouldBe(ID, IDENT, THING, NULL, VOID, MISSING, EMPTY, NOW, DATE, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, REGEX, JSON, ARRAY, PARAM)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
}
one.LHS, err = p.declare(tok, lit)
if err != nil {
return nil, err
}
tok, lit, err = p.shouldBe(IS, IN, EQ, NEQ, EEQ, NEE, ANY, LT, LTE, GT, GTE, SIN, SNI, INS, NIS, CONTAINS, CONTAINSALL, CONTAINSNONE, CONTAINSSOME, ALLCONTAINEDIN, NONECONTAINEDIN, SOMECONTAINEDIN)
if err != nil {
return nil, err
}
one.Op = tok
if tok == IN {
one.Op = INS
}
if tok == IS {
one.Op = EQ
if _, _, exi := p.mightBe(NOT); exi {
one.Op = NEQ
}
if _, _, exi := p.mightBe(IN); exi {
switch one.Op {
case EQ:
one.Op = INS
case NEQ:
one.Op = NIS
}
}
}
if tok == CONTAINS {
one.Op = SIN
if _, _, exi := p.mightBe(NOT); exi {
one.Op = SNI
}
}
tok, lit, err = p.shouldBe(ID, IDENT, THING, NULL, VOID, MISSING, EMPTY, NOW, DATE, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, REGEX, JSON, ARRAY, PARAM)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field value"}}
}
one.RHS, err = p.declare(tok, lit)
if err != nil {
return nil, err
}
mul = append(mul, one)
if _, _, exi := p.mightBe(AND, OR); !exi {
break
}
}
return mul, nil
}

View file

@ -50,12 +50,16 @@ func (p *parser) parseData() (exp []Expr, err error) {
func (p *parser) parseSet() (mul []Expr, err error) { func (p *parser) parseSet() (mul []Expr, err error) {
var tok Token
var lit string
for { for {
one := &BinaryExpression{} var tok Token
var lit string
one := &DataExpression{}
// The first part of a SET expression must
// always be an identifier, specifying a
// record field to set.
tok, lit, err = p.shouldBe(IDENT) tok, lit, err = p.shouldBe(IDENT)
if err != nil { if err != nil {
@ -67,32 +71,40 @@ func (p *parser) parseSet() (mul []Expr, err error) {
return nil, err return nil, err
} }
tok, lit, err = p.shouldBe(EQ, INC, DEC) // The next query part must be a =, +=, or
// -= operator, as this is a SET expression
// and not a binary expression.
one.Op, lit, err = p.shouldBe(EQ, INC, DEC)
if err != nil { if err != nil {
return nil, err return nil, err
} }
one.Op = tok
tok, lit, err = p.shouldBe(IDENT, THING, NULL, VOID, NOW, DATE, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, JSON, ARRAY, PARAM) // The next query part can be any expression
if err != nil { // including a parenthesised expression or a
return nil, &ParseError{Found: lit, Expected: []string{"field value"}} // binary expression so handle accordingly.
}
one.RHS, err = p.declare(tok, lit) one.RHS, err = p.parseExpr()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Append the single SET data expression to
// the array of data expressions.
mul = append(mul, one) mul = append(mul, one)
// Check to see if the next token is a comma
// and if not, then break out of the loop,
// otherwise repeat until we find no comma.
if _, _, exi := p.mightBe(COMMA); !exi { if _, _, exi := p.mightBe(COMMA); !exi {
p.unscan()
break break
} }
} }
return mul, nil return
} }

View file

@ -16,7 +16,10 @@ package sql
func (p *parser) parseEcho() (exp Token, err error) { func (p *parser) parseEcho() (exp Token, err error) {
// Next token might be RETURN // The next token that we expect to see is a
// RETURN token, and if we don't find one then
// return nil, with no error.
if _, _, exi := p.mightBe(RETURN); exi { if _, _, exi := p.mightBe(RETURN); exi {
exp, _, err = p.shouldBe(ID, NONE, INFO, BOTH, DIFF, BEFORE, AFTER) exp, _, err = p.shouldBe(ID, NONE, INFO, BOTH, DIFF, BEFORE, AFTER)

View file

@ -15,6 +15,7 @@
package sql package sql
import ( import (
"fmt"
"regexp" "regexp"
"time" "time"
) )
@ -43,7 +44,10 @@ func (p *parser) parseWhat() (mul []Expr, err error) {
mul = append(mul, one) mul = append(mul, one)
} }
// If the next token is not a comma then break the loop. // Check to see if the next token is a comma
// and if not, then break out of the loop,
// otherwise repeat until we find no comma.
if _, _, exi := p.mightBe(COMMA); !exi { if _, _, exi := p.mightBe(COMMA); !exi {
break break
} }
@ -78,7 +82,10 @@ func (p *parser) parseNames() (mul []string, err error) {
mul = append(mul, one) mul = append(mul, one)
// If the next token is not a comma then break the loop. // Check to see if the next token is a comma
// and if not, then break out of the loop,
// otherwise repeat until we find no comma.
if _, _, exi := p.mightBe(COMMA); !exi { if _, _, exi := p.mightBe(COMMA); !exi {
break break
} }
@ -93,6 +100,24 @@ func (p *parser) parseNames() (mul []string, err error) {
// //
// -------------------------------------------------- // --------------------------------------------------
func (p *parser) parseCond() (exp Expr, err error) {
// The next token that we expect to see is a
// WHERE token, and if we don't find one then
// return nil, with no error.
if _, _, exi := p.mightBe(WHERE); !exi {
return nil, nil
}
return p.parseExpr()
}
// --------------------------------------------------
//
// --------------------------------------------------
func (p *parser) parseIdent() (*Ident, error) { func (p *parser) parseIdent() (*Ident, error) {
_, lit, err := p.shouldBe(IDENT) _, lit, err := p.shouldBe(IDENT)
@ -249,62 +274,316 @@ func (p *parser) parseDuration() (time.Duration, error) {
} }
func (p *parser) parseDefault() (interface{}, error) { func (p *parser) parseExpr() (exp Expr, err error) {
tok, lit, err := p.shouldBe(NULL, NOW, DATE, TIME, TRUE, FALSE, NUMBER, DOUBLE, STRING, REGION, IDENT, THING, ARRAY, JSON) // Create the root binary expression tree.
root := &BinaryExpression{}
// If the subsequent token is an in, out, or
// multi way path expression, then parse all
// following expressions as a path.
if tok, _, exi := p.mightBe(OEDGE, IEDGE, BEDGE); exi {
return p.parsePath(tok)
}
// Begin with parsing the first expression
// as the root of the tree to start with.
root.RHS, err = p.parsePart()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return p.declare(tok, lit) // If the subsequent token is an in, out, or
// multi way path expression, then parse all
// following expressions as a path.
} if tok, _, exi := p.mightBe(OEDGE, IEDGE, BEDGE); exi {
return p.parsePath(root.RHS, tok)
}
func (p *parser) parseExpr() (mul []*Field, err error) { // Loop over the operations and expressions
// and build a binary expression tree based
var tok Token // on the precedence of the operators.
var lit string
var exi bool
var val interface{}
for { for {
one := &Field{} var rhs Expr
tok, lit, err = p.shouldBe(IDENT, ID, NOW, PATH, NULL, ALL, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, JSON, ARRAY, PARAM) // Get the next token from the scanner and
if err != nil { // the literal value that it is scanned as.
return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
}
one.Expr, err = p.declare(tok, lit) tok, lit, _ := p.scan()
if err != nil {
return
}
one.Alias = lit switch tok {
// Next token might be AS // If the token is an AND or OR expression
if _, _, exi = p.mightBe(AS); exi { // then skip to the next expression without
// further checks.
_, lit, err = p.shouldBe(IDENT) case AND, OR:
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field alias"}} // If the token is not an operator but can
// be converted into an operator based on
// logic, then convert it to an operator.
case IN:
tok = INS
if _, _, exi := p.mightBe(NOT); exi {
tok = NIS
} }
val, err = p.declare(STRING, lit) case CONTAINS:
if err != nil {
return tok = SIN
if _, _, exi := p.mightBe(NOT); exi {
tok = SNI
} }
one.Alias = val.(string) case IS:
tok = EQ
if _, _, exi := p.mightBe(NOT); exi {
tok = NEQ
}
if _, _, exi := p.mightBe(IN); exi {
switch tok {
case EQ:
tok = INS
case NEQ:
tok = NIS
}
}
// If the token is a keyword which is also
// actually an operator, then skip to the
// next expression without further checks.
case CONTAINSALL, CONTAINSNONE, CONTAINSSOME:
case ALLCONTAINEDIN, NONECONTAINEDIN, SOMECONTAINEDIN:
// If the token is an int64 or a float64 then
// check to see whether the first rune is a
// + or a - and use it as a token instead.
case NUMBER, DOUBLE:
switch lit[0] {
case '-':
rhs, err = p.declare(tok, lit[1:])
tok = SUB
case '+':
rhs, err = p.declare(tok, lit[1:])
tok = ADD
default:
p.unscan()
return root.RHS, nil
}
// Check to see if the token is an operator
// expression. If it is none of those then
// unscan and break out of the loop.
default:
if !tok.isOperator() {
p.unscan()
return root.RHS, nil
}
} }
mul = append(mul, one) // If the token was not an int64 or float64
// signed value then retrieve the next part
// of the expression and add it to the right.
// If the next token is not a comma then break the loop. if rhs == nil {
if _, _, exi = p.mightBe(COMMA); !exi { rhs, err = p.parsePart()
break if err != nil {
return nil, err
}
}
// Find the right place in the tree to add the
// new expression, by descending the right side
// of the tree until we reach the last binary
// expression, or until we reach an expression
// whose operator precendence >= this precedence.
for node := root; ; {
r, ok := node.RHS.(*BinaryExpression)
if !ok || r.Op.precedence() >= tok.precedence() {
node.RHS = &BinaryExpression{LHS: node.RHS, Op: tok, RHS: rhs}
break
}
node = r
}
}
return root, err
}
func (p *parser) parsePart() (exp Expr, err error) {
toks := []Token{
MUL, ID, IDENT, THING,
NULL, VOID, EMPTY, MISSING,
TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE,
DATE, TIME, DURATION, JSON, ARRAY, PARAM, LPAREN,
}
tok, lit, _ := p.scan()
// We need to declare the type up here instead
// of at the bottom, as the held value might
// be overwritten by the next token scan.
exp, err = p.declare(tok, lit)
if err != nil {
return nil, err
}
// If the current token is a left parenthesis
// bracket, then we will parse this complete
// expression part as a subquery.
if p.is(tok, LPAREN) {
return p.parseSubq()
}
// If the next token is a left parenthesis
// bracket, then we will parse this complete
// expression part as a function call.
if _, _, exi := p.mightBe(LPAREN); exi {
return p.parseCall(lit)
}
// If this expression is not a subquery or a
// function call, then check to see if the
// token is in the list of allowed tokens.
if !p.in(tok, toks) {
err = &ParseError{Found: lit, Expected: []string{"expression"}}
}
return
}
func (p *parser) parseSubq() (sub *SubExpression, err error) {
var exp Expr
var tok Token
tok, _, _ = p.mightBe(SELECT, CREATE, UPDATE, DELETE, RELATE)
switch tok {
default:
exp, err = p.parseExpr()
case SELECT:
exp, err = p.parseSelectStatement()
case CREATE:
exp, err = p.parseCreateStatement()
case UPDATE:
exp, err = p.parseUpdateStatement()
case DELETE:
exp, err = p.parseDeleteStatement()
case RELATE:
exp, err = p.parseRelateStatement()
}
p.mightBe(RPAREN)
return &SubExpression{Expr: exp}, err
}
func (p *parser) parseCall(name string) (fnc *FuncExpression, err error) {
fnc = &FuncExpression{Name: name}
// Check to see if the immediate token
// is a right parenthesis bracket, and if
// it is then this function has no args.
if _, _, exi := p.mightBe(RPAREN); !exi {
for {
var arg Expr
arg, err = p.parseExpr()
if err != nil {
return nil, err
}
// Append the single expression to the array
// of function argument expressions.
fnc.Args = append(fnc.Args, arg)
// Check to see if the next token is a comma
// and if not, then break out of the loop,
// otherwise repeat until we find no comma.
if _, _, exi := p.mightBe(COMMA); !exi {
break
}
}
_, _, err = p.shouldBe(RPAREN)
}
// Check to see if the used function name is
// valid according to the currently supported
// functions. If not then return an error.
if _, ok := funcs[fnc.Name]; !ok {
return nil, &ParseError{
Found: fmt.Sprintf("%s()", name),
Expected: []string{"valid function name"},
}
}
// Check to see if the number of arguments
// is correct for the specified function name,
// and if not, then return an error.
if _, ok := funcs[fnc.Name][len(fnc.Args)]; !ok {
s, t := "", len(funcs[fnc.Name])
for i := 0; i < t; i++ {
switch {
case i > 0 && i == t-1:
s = s + " or "
case i > 0:
s = s + ", "
}
s = s + fmt.Sprintf("%d", i)
}
switch {
case t == 1:
s = s + " argument"
case t >= 2:
s = s + " arguments"
}
return nil, &ParseError{
Found: fmt.Sprintf("%s() with %d arguments", fnc.Name, len(fnc.Args)),
Expected: []string{s},
} }
} }
@ -312,3 +591,165 @@ func (p *parser) parseExpr() (mul []*Field, err error) {
return return
} }
func (p *parser) parsePath(expr ...Expr) (path *PathExpression, err error) {
path = &PathExpression{}
// Take the previosuly scanned expression
// and append it to the path expression
// tree as the first item.
for _, e := range expr {
switch v := e.(type) {
case Token:
path.Expr = append(path.Expr, &JoinExpression{Join: v})
default:
path.Expr = append(path.Expr, &PartExpression{Part: v})
}
}
// If the last expression passed in was a
// path joiner (->, <-, or <->), then we
// need to process a path part first.
if _, ok := expr[len(expr)-1].(Token); ok {
var part Expr
part, err = p.parseStep()
if err != nil {
return nil, err
}
if part == nil {
return
}
path.Expr = append(path.Expr, &PartExpression{Part: part})
}
for {
var join Expr
var part Expr
// We expect the next token to be a join
// operator (->, <-, or <->), otherwise we
// are at the end of the path and will
// ignore it and return.
join, err = p.parseJoin()
if err != nil {
return nil, err
}
if join == nil {
return
}
path.Expr = append(path.Expr, &JoinExpression{Join: join.(Token)})
// We expect the next token to be a path
// part identifier, otherwise we are at
// the end of the path and will ignore it
// and return.
part, err = p.parseStep()
if err != nil {
return nil, err
}
if part == nil {
return
}
path.Expr = append(path.Expr, &PartExpression{Part: part})
}
return
}
func (p *parser) parseJoin() (exp Expr, err error) {
toks := []Token{
OEDGE, IEDGE, BEDGE,
}
tok, _, _ := p.scan()
if !p.in(tok, toks) {
p.unscan()
return
}
return tok, err
}
func (p *parser) parseStep() (exp Expr, err error) {
toks := []Token{
QMARK, IDENT, THING, PARAM, LPAREN,
}
tok, lit, _ := p.scan()
// We need to declare the type up here instead
// of at the bottom, as the held value might
// be overwritten by the next token scan.
exp, err = p.declare(tok, lit)
if err != nil {
return nil, err
}
// If the current token is a left parenthesis
// bracket, then we will parse this complete
// expression part as a subquery.
if p.is(tok, LPAREN) {
return p.parseSubp()
}
// If this expression is not a sub-path
// expression, then check to see if the
// token is in the list of allowed tokens.
if !p.in(tok, toks) {
p.unscan()
exp = nil
}
return
}
func (p *parser) parseSubp() (stmt *SubpExpression, err error) {
stmt = &SubpExpression{}
if stmt.What, err = p.parseWhat(); err != nil {
return nil, err
}
if _, _, exi := p.mightBe(AS); exi {
if stmt.Name, err = p.parseName(); err != nil {
return nil, err
}
}
if stmt.Cond, err = p.parseCond(); err != nil {
return nil, err
}
if _, _, err = p.shouldBe(RPAREN); err != nil {
return nil, err
}
return
}

View file

@ -78,7 +78,7 @@ func (p *parser) parseDefineFieldStatement() (stmt *DefineFieldStatement, err er
} }
if p.is(tok, DEFAULT) { if p.is(tok, DEFAULT) {
if stmt.Default, err = p.parseDefault(); err != nil { if stmt.Default, err = p.parseExpr(); err != nil {
return nil, err return nil, err
} }
} }

View file

@ -21,26 +21,35 @@ func (p *parser) parseLetStatement() (stmt *LetStatement, err error) {
stmt.KV = p.c.Get("KV").(string) stmt.KV = p.c.Get("KV").(string)
stmt.NS = p.c.Get("NS").(string) stmt.NS = p.c.Get("NS").(string)
stmt.DB = p.c.Get("DB").(string) stmt.DB = p.c.Get("DB").(string)
// The first part of a LET expression must
// always be an identifier, specifying a
// variable name to set.
_, stmt.Name, err = p.shouldBe(IDENT) _, stmt.Name, err = p.shouldBe(IDENT)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// The next query part must always be a =
// operator, as this is a LET expression
// and not a binary expression.
_, _, err = p.shouldBe(EQ) _, _, err = p.shouldBe(EQ)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tok, lit, err := p.shouldBe(NULL, NOW, DATE, TIME, TRUE, FALSE, STRING, NUMBER, DOUBLE, THING, JSON, ARRAY, PARAM) // The next query part can be any expression
// including a parenthesised expression or a
// binary expression so handle accordingly.
stmt.What, err = p.parseExpr()
if err != nil { if err != nil {
return nil, err return nil, err
} }
stmt.Expr, err = p.declare(tok, lit) // Check that we have reached the end of the
if err != nil { // statement with either a ';' or EOF.
return nil, err
}
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil { if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err return nil, err

View file

@ -115,7 +115,7 @@ func (p *parser) parseMulti() (*Query, error) {
// parseSingle parses a single SQL SELECT statement. // parseSingle parses a single SQL SELECT statement.
func (p *parser) parseSingle() (Statement, error) { func (p *parser) parseSingle() (Statement, error) {
tok, _, err := p.shouldBe(USE, LET, INFO, BEGIN, CANCEL, COMMIT, RETURN, SELECT, CREATE, UPDATE, DELETE, RELATE, DEFINE, REMOVE) tok, _, err := p.shouldBe(USE, INFO, BEGIN, CANCEL, COMMIT, LET, RETURN, SELECT, CREATE, UPDATE, DELETE, RELATE, DEFINE, REMOVE)
switch tok { switch tok {

View file

@ -18,27 +18,22 @@ func (p *parser) parseReturnStatement() (stmt *ReturnStatement, err error) {
stmt = &ReturnStatement{} stmt = &ReturnStatement{}
for { if _, _, _, err = p.o.get(AuthTB); err != nil {
return nil, err
tok, lit, err := p.shouldBe(NULL, NOW, DATE, TIME, TRUE, FALSE, STRING, NUMBER, DOUBLE, THING, JSON, ARRAY, PARAM)
if err != nil {
return nil, err
}
val, err := p.declare(tok, lit)
if err != nil {
return nil, err
}
stmt.What = append(stmt.What, val)
// If the next token is not a comma then break the loop.
if _, _, exi := p.mightBe(COMMA); !exi {
break
}
} }
// The next query part can be any expression
// including a parenthesised expression or a
// binary expression so handle accordingly.
stmt.What, err = p.parseExpr()
if err != nil {
return nil, err
}
// Check that we have reached the end of the
// statement with either a ';' or EOF.
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil { if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err return nil, err
} }

View file

@ -63,7 +63,7 @@ func (s *scanner) scan() (tok Token, lit string, val interface{}) {
case eof: case eof:
return EOF, "", val return EOF, "", val
case '*': case '*':
return ALL, string(ch), val return MUL, string(ch), val
case '×': case '×':
return MUL, string(ch), val return MUL, string(ch), val
case '∙': case '∙':
@ -135,6 +135,9 @@ func (s *scanner) scan() (tok Token, lit string, val interface{}) {
return s.scanCommentSingle(ch) return s.scanCommentSingle(ch)
case chn == '*': case chn == '*':
return s.scanCommentMultiple(ch) return s.scanCommentMultiple(ch)
case isNumber(chn):
s.undo()
return DIV, string(ch), val
case chn == ' ': case chn == ' ':
s.undo() s.undo()
return DIV, string(ch), val return DIV, string(ch), val
@ -759,7 +762,7 @@ func isLetter(ch rune) bool {
// isIdentChar returns true if the rune is allowed in a IDENT. // isIdentChar returns true if the rune is allowed in a IDENT.
func isIdentChar(ch rune) bool { func isIdentChar(ch rune) bool {
return isLetter(ch) || isNumber(ch) || ch == '.' || ch == '_' || ch == '*' return isLetter(ch) || isNumber(ch) || ch == '.' || ch == '_' || ch == '*' || ch == '[' || ch == ']'
} }
// isThingChar returns true if the rune is allowed in a THING. // isThingChar returns true if the rune is allowed in a THING.

View file

@ -71,32 +71,141 @@ func (p *parser) parseSelectStatement() (stmt *SelectStatement, err error) {
} }
func (p *parser) parseField() (mul []*Field, err error) {
var lit string
var exi bool
for {
one := &Field{}
one.Expr, err = p.parseExpr()
if err != nil {
return
}
one.Alias = "*" // TODO need to implement default field name
// Chec to see if the next token is an AS
// clause, and if it is read the defined
// field alias name from the scanner.
if _, _, exi = p.mightBe(AS); exi {
if _, one.Alias, err = p.shouldBe(IDENT); err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field alias"}}
}
}
// Append the single expression to the array
// of return statement expressions.
mul = append(mul, one)
// Check to see if the next token is a comma
// and if not, then break out of the loop,
// otherwise repeat until we find no comma.
if _, _, exi = p.mightBe(COMMA); !exi {
break
}
}
return
}
func (p *parser) parseWhere() (exp Expr, err error) {
// The next token that we expect to see is a
// WHERE token, and if we don't find one then
// return nil, with no error.
if _, _, exi := p.mightBe(WHERE); !exi {
return nil, nil
}
return p.parseExpr()
}
func (p *parser) parseGroup() (mul []*Group, err error) { func (p *parser) parseGroup() (mul []*Group, err error) {
// The next token that we expect to see is a
// GROUP token, and if we don't find one then
// return nil, with no error.
if _, _, exi := p.mightBe(GROUP); !exi { if _, _, exi := p.mightBe(GROUP); !exi {
return nil, nil return nil, nil
} }
// We don't need to have a BY token, but we
// allow it so that the SQL query would read
// better when compared to english.
_, _, _ = p.mightBe(BY) _, _, _ = p.mightBe(BY)
for {
var tok Token
var lit string
one := &Group{}
tok, lit, err = p.shouldBe(IDENT, ID)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
}
one.Expr, err = p.declare(tok, lit)
if err != nil {
return nil, err
}
// Append the single expression to the array
// of return statement expressions.
mul = append(mul, one)
// Check to see if the next token is a comma
// and if not, then break out of the loop,
// otherwise repeat until we find no comma.
if _, _, exi := p.mightBe(COMMA); !exi {
break
}
}
return return
} }
func (p *parser) parseOrder() (mul []*Order, err error) { func (p *parser) parseOrder() (mul []*Order, err error) {
var tok Token // The next token that we expect to see is a
var lit string // ORDER token, and if we don't find one then
var exi bool // return nil, with no error.
if _, _, exi := p.mightBe(ORDER); !exi { if _, _, exi := p.mightBe(ORDER); !exi {
return nil, nil return nil, nil
} }
// We don't need to have a BY token, but we
// allow it so that the SQL query would read
// better when compared to english.
_, _, _ = p.mightBe(BY) _, _, _ = p.mightBe(BY)
for { for {
var exi bool
var tok Token
var lit string
one := &Order{} one := &Order{}
tok, lit, err = p.shouldBe(IDENT, ID) tok, lit, err = p.shouldBe(IDENT, ID)
@ -109,10 +218,8 @@ func (p *parser) parseOrder() (mul []*Order, err error) {
return nil, err return nil, err
} }
tok, lit, exi = p.mightBe(ASC, DESC) if tok, lit, exi = p.mightBe(ASC, DESC); !exi {
if !exi {
tok = ASC tok = ASC
lit = tok.String()
} }
one.Dir, err = p.declare(tok, lit) one.Dir, err = p.declare(tok, lit)
@ -120,9 +227,15 @@ func (p *parser) parseOrder() (mul []*Order, err error) {
return nil, err return nil, err
} }
// Append the single expression to the array
// of return statement expressions.
mul = append(mul, one) mul = append(mul, one)
// If the next token is not a comma then break the loop. // Check to see if the next token is a comma
// and if not, then break out of the loop,
// otherwise repeat until we find no comma.
if _, _, exi := p.mightBe(COMMA); !exi { if _, _, exi := p.mightBe(COMMA); !exi {
break break
} }
@ -135,10 +248,18 @@ func (p *parser) parseOrder() (mul []*Order, err error) {
func (p *parser) parseLimit() (Expr, error) { func (p *parser) parseLimit() (Expr, error) {
// The next token that we expect to see is a
// LIMIT token, and if we don't find one then
// return nil, with no error.
if _, _, exi := p.mightBe(LIMIT); !exi { if _, _, exi := p.mightBe(LIMIT); !exi {
return nil, nil return nil, nil
} }
// We don't need to have a BY token, but we
// allow it so that the SQL query would read
// better when compared to english.
_, _, _ = p.mightBe(BY) _, _, _ = p.mightBe(BY)
tok, lit, err := p.shouldBe(NUMBER) tok, lit, err := p.shouldBe(NUMBER)
@ -152,17 +273,23 @@ func (p *parser) parseLimit() (Expr, error) {
func (p *parser) parseStart() (Expr, error) { func (p *parser) parseStart() (Expr, error) {
// Remove the START keyword // The next token that we expect to see is a
// START token, and if we don't find one then
// return nil, with no error.
if _, _, exi := p.mightBe(START); !exi { if _, _, exi := p.mightBe(START); !exi {
return nil, nil return nil, nil
} }
// Next token might be AT // We don't need to have a AT token, but we
// allow it so that the SQL query would read
// better when compared to english.
_, _, _ = p.mightBe(AT) _, _, _ = p.mightBe(AT)
tok, lit, err := p.shouldBe(NUMBER, THING) tok, lit, err := p.shouldBe(NUMBER)
if err != nil { if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"number or record id"}} return nil, &ParseError{Found: lit, Expected: []string{"start number"}}
} }
return p.declare(tok, lit) return p.declare(tok, lit)
@ -171,7 +298,6 @@ func (p *parser) parseStart() (Expr, error) {
func (p *parser) parseVersion() (Expr, error) { func (p *parser) parseVersion() (Expr, error) {
// Remove the VERSION keyword
if _, _, exi := p.mightBe(VERSION, ON); !exi { if _, _, exi := p.mightBe(VERSION, ON); !exi {
return nil, nil return nil, nil
} }

View file

@ -90,9 +90,12 @@ func (p *parser) declare(tok Token, lit string) (interface{}, error) {
case EMPTY: case EMPTY:
return &Empty{}, nil return &Empty{}, nil
case ALL: case MUL:
return &All{}, nil return &All{}, nil
case QMARK:
return &Any{}, nil
case ASC: case ASC:
return &Asc{}, nil return &Asc{}, nil

View file

@ -36,7 +36,7 @@ func (p *parser) parseDefineViewStatement() (stmt *DefineViewStatement, err erro
return nil, err return nil, err
} }
if stmt.Expr, err = p.parseExpr(); err != nil { if stmt.Expr, err = p.parseField(); err != nil {
return nil, err return nil, err
} }

View file

@ -24,14 +24,12 @@ import (
"github.com/abcum/surreal/util/data" "github.com/abcum/surreal/util/data"
) )
func (this *Doc) Check(cond []sql.Expr) (val bool) { func (this *Doc) Check(cond sql.Expr) (val bool) {
for _, part := range cond { switch expr := cond.(type) {
switch expr := part.(type) { case *sql.BinaryExpression:
case *sql.BinaryExpression: if !this.chkOne(expr) {
if !this.chkOne(expr) { return false
return false
}
} }
} }

View file

@ -44,7 +44,7 @@ func (this *Doc) Merge(data []sql.Expr) (err error) {
switch expr := part.(type) { switch expr := part.(type) {
case *sql.DiffExpression: case *sql.DiffExpression:
this.mrgDpm(expr) this.mrgDpm(expr)
case *sql.BinaryExpression: case *sql.DataExpression:
this.mrgOne(expr) this.mrgOne(expr)
case *sql.MergeExpression: case *sql.MergeExpression:
this.mrgAny(expr) this.mrgAny(expr)
@ -149,7 +149,7 @@ func (this *Doc) mrgDpm(expr *sql.DiffExpression) {
} }
func (this *Doc) mrgOne(expr *sql.BinaryExpression) { func (this *Doc) mrgOne(expr *sql.DataExpression) {
lhs := this.getMrgItemLHS(expr.LHS) lhs := this.getMrgItemLHS(expr.LHS)
rhs := this.getMrgItemRHS(expr.RHS) rhs := this.getMrgItemRHS(expr.RHS)