diff --git a/db/let.go b/db/let.go index 09d71220..c4e319ea 100644 --- a/db/let.go +++ b/db/let.go @@ -21,11 +21,11 @@ import ( 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: - e.Set(ast.Name, expr) + e.Set(ast.Name, what) case *sql.Param: - e.Set(ast.Name, e.Get(expr.ID)) + e.Set(ast.Name, e.Get(what.ID)) } return diff --git a/db/return.go b/db/return.go index ee82d313..ccf26ec0 100644 --- a/db/return.go +++ b/db/return.go @@ -21,15 +21,11 @@ import ( func (e *executor) executeReturnStatement(txn kvs.TX, ast *sql.ReturnStatement) (out []interface{}, err error) { - for _, w := range ast.What { - - switch what := w.(type) { - default: - out = append(out, what) - case *sql.Param: - out = append(out, e.ctx.Get(what.ID).Data()) - } - + switch what := ast.What.(type) { + default: + out = append(out, what) + case *sql.Param: + out = append(out, e.ctx.Get(what.ID).Data()) } return diff --git a/sql/ast.go b/sql/ast.go index 725e3d15..2c4b727b 100644 --- a/sql/ast.go +++ b/sql/ast.go @@ -48,11 +48,6 @@ type CancelStatement struct{} // UseStatement represents a SQL COMMIT TRANSACTION statement. type CommitStatement struct{} -// ReturnStatement represents a SQL RETURN statement. -type ReturnStatement struct { - What []Expr -} - // -------------------------------------------------- // Use // -------------------------------------------------- @@ -81,11 +76,13 @@ type InfoStatement struct { // LetStatement represents a SQL LET statement. type LetStatement struct { - KV string `cork:"-" codec:"-"` - NS string `cork:"-" codec:"-"` - DB 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:"-"` Expr []*Field `cork:"expr" codec:"expr"` What []Expr `cork:"what" codec:"what"` - Cond []Expr `cork:"cond" codec:"cond"` + Cond Expr `cork:"cond" codec:"cond"` Echo Token `cork:"echo" codec:"echo"` } @@ -110,7 +107,7 @@ type SelectStatement struct { DB string `cork:"-" codec:"-"` Expr []*Field `cork:"expr" codec:"expr"` What []Expr `cork:"what" codec:"what"` - Cond []Expr `cork:"cond" codec:"cond"` + Cond Expr `cork:"cond" codec:"cond"` Group []*Group `cork:"group" codec:"group"` Order []*Order `cork:"order" codec:"order"` Limit Expr `cork:"limit" codec:"limit"` @@ -136,7 +133,7 @@ type UpdateStatement struct { DB string `cork:"-" codec:"-"` What []Expr `cork:"what" codec:"what"` Data []Expr `cork:"data" codec:"data"` - Cond []Expr `cork:"cond" codec:"cond"` + Cond Expr `cork:"cond" codec:"cond"` Echo Token `cork:"echo" codec:"echo"` } @@ -147,7 +144,7 @@ type DeleteStatement struct { DB string `cork:"-" codec:"-"` Hard bool `cork:"hard" codec:"hard"` What []Expr `cork:"what" codec:"what"` - Cond []Expr `cork:"cond" codec:"cond"` + Cond Expr `cork:"cond" codec:"cond"` Echo Token `cork:"echo" codec:"echo"` } @@ -156,10 +153,11 @@ type RelateStatement struct { KV string `cork:"-" codec:"-"` NS 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"` - To []Expr `cork:"to" codec:"to"` + With []Expr `cork:"with" codec:"with"` Data []Expr `cork:"data" codec:"data"` + Uniq bool `cork:"uniq" codec:"uniq"` Echo Token `cork:"echo" codec:"echo"` } @@ -218,7 +216,7 @@ type DefineRulesStatement struct { What []string `cork:"-" codec:"-"` When []string `cork:"-" codec:"-"` 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. @@ -299,7 +297,7 @@ type DefineViewStatement struct { Name string `cork:"name" codec:"name"` Expr []*Field `cork:"expr" codec:"expr"` What []Expr `cork:"what" codec:"what"` - Cond []Expr `cork:"cond" codec:"cond"` + Cond Expr `cork:"cond" codec:"cond"` Group []*Group `cork:"group" codec:"group"` } @@ -318,9 +316,12 @@ type RemoveViewStatement struct { // Expr represents a sql expression type Expr interface{} -// All represents a wildcard expression. +// All represents a * expression. type All struct{} +// Any represents a ? expression. +type Any struct{} + // Asc represents the ASC expression. type Asc struct{} @@ -373,19 +374,58 @@ type Order struct { Dir Expr } -// ClosedExpression represents a parenthesized expression. -type ClosedExpression struct { +// -------------------------------------------------- +// Expressions +// -------------------------------------------------- + +// SubExpression represents a subquery. +type SubExpression struct { 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 { LHS Expr Op Token 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 { JSON interface{} } diff --git a/sql/cond.go b/sql/cond.go deleted file mode 100644 index 7ddd2358..00000000 --- a/sql/cond.go +++ /dev/null @@ -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 - -} diff --git a/sql/data.go b/sql/data.go index fc16d2ea..13b81a79 100644 --- a/sql/data.go +++ b/sql/data.go @@ -50,12 +50,16 @@ func (p *parser) parseData() (exp []Expr, err error) { func (p *parser) parseSet() (mul []Expr, err error) { - var tok Token - var lit string - 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) if err != nil { @@ -67,32 +71,40 @@ func (p *parser) parseSet() (mul []Expr, err error) { 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 { 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) - if err != nil { - return nil, &ParseError{Found: lit, Expected: []string{"field value"}} - } + // The next query part can be any expression + // including a parenthesised expression or a + // binary expression so handle accordingly. - one.RHS, err = p.declare(tok, lit) + one.RHS, err = p.parseExpr() if err != nil { return nil, err } + // Append the single SET data expression to + // the array of data 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 { - p.unscan() break } } - return mul, nil + return } diff --git a/sql/echo.go b/sql/echo.go index 011a671b..fa01d3e9 100644 --- a/sql/echo.go +++ b/sql/echo.go @@ -16,7 +16,10 @@ package sql 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 { exp, _, err = p.shouldBe(ID, NONE, INFO, BOTH, DIFF, BEFORE, AFTER) diff --git a/sql/exprs.go b/sql/exprs.go index 51e4b9a6..02dca793 100644 --- a/sql/exprs.go +++ b/sql/exprs.go @@ -15,6 +15,7 @@ package sql import ( + "fmt" "regexp" "time" ) @@ -43,7 +44,10 @@ func (p *parser) parseWhat() (mul []Expr, err error) { 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 { break } @@ -78,7 +82,10 @@ func (p *parser) parseNames() (mul []string, err error) { 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 { 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) { _, 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 { 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) { - - var tok Token - var lit string - var exi bool - var val interface{} + // Loop over the operations and expressions + // and build a binary expression tree based + // on the precedence of the operators. 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) - if err != nil { - return nil, &ParseError{Found: lit, Expected: []string{"field name"}} - } + // Get the next token from the scanner and + // the literal value that it is scanned as. - one.Expr, err = p.declare(tok, lit) - if err != nil { - return - } + tok, lit, _ := p.scan() - one.Alias = lit + switch tok { - // Next token might be AS - if _, _, exi = p.mightBe(AS); exi { + // If the token is an AND or OR expression + // then skip to the next expression without + // further checks. - _, lit, err = p.shouldBe(IDENT) - if err != nil { - return nil, &ParseError{Found: lit, Expected: []string{"field alias"}} + case AND, OR: + + // 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) - if err != nil { - return + case CONTAINS: + + 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 _, _, exi = p.mightBe(COMMA); !exi { - break + if rhs == nil { + rhs, err = p.parsePart() + 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 } + +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 + +} diff --git a/sql/field.go b/sql/field.go index b4218f08..912e23a7 100644 --- a/sql/field.go +++ b/sql/field.go @@ -78,7 +78,7 @@ func (p *parser) parseDefineFieldStatement() (stmt *DefineFieldStatement, err er } if p.is(tok, DEFAULT) { - if stmt.Default, err = p.parseDefault(); err != nil { + if stmt.Default, err = p.parseExpr(); err != nil { return nil, err } } diff --git a/sql/let.go b/sql/let.go index b981f205..5d720ff0 100644 --- a/sql/let.go +++ b/sql/let.go @@ -21,26 +21,35 @@ func (p *parser) parseLetStatement() (stmt *LetStatement, err error) { stmt.KV = p.c.Get("KV").(string) stmt.NS = p.c.Get("NS").(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) if err != nil { 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) if err != nil { 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 { return nil, err } - stmt.Expr, err = p.declare(tok, lit) - 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 { return nil, err diff --git a/sql/parser.go b/sql/parser.go index 84a0fbec..6d999d4b 100644 --- a/sql/parser.go +++ b/sql/parser.go @@ -115,7 +115,7 @@ func (p *parser) parseMulti() (*Query, error) { // parseSingle parses a single SQL SELECT statement. 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 { diff --git a/sql/return.go b/sql/return.go index 8584c683..f5477565 100644 --- a/sql/return.go +++ b/sql/return.go @@ -18,27 +18,22 @@ func (p *parser) parseReturnStatement() (stmt *ReturnStatement, err error) { stmt = &ReturnStatement{} - for { - - 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 - } - + if _, _, _, err = p.o.get(AuthTB); err != nil { + return nil, err } + // 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 { return nil, err } diff --git a/sql/scanner.go b/sql/scanner.go index 36d00c35..5430a029 100644 --- a/sql/scanner.go +++ b/sql/scanner.go @@ -63,7 +63,7 @@ func (s *scanner) scan() (tok Token, lit string, val interface{}) { case eof: return EOF, "", val case '*': - return ALL, string(ch), val + return MUL, string(ch), val case '×': return MUL, string(ch), val case '∙': @@ -135,6 +135,9 @@ func (s *scanner) scan() (tok Token, lit string, val interface{}) { return s.scanCommentSingle(ch) case chn == '*': return s.scanCommentMultiple(ch) + case isNumber(chn): + s.undo() + return DIV, string(ch), val case chn == ' ': s.undo() 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. 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. diff --git a/sql/select.go b/sql/select.go index a0693250..2086eda7 100644 --- a/sql/select.go +++ b/sql/select.go @@ -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) { + // 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 { 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) + 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 } func (p *parser) parseOrder() (mul []*Order, err error) { - var tok Token - var lit string - var exi bool + // The next token that we expect to see is a + // ORDER token, and if we don't find one then + // return nil, with no error. if _, _, exi := p.mightBe(ORDER); !exi { 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) for { + var exi bool + var tok Token + var lit string + one := &Order{} tok, lit, err = p.shouldBe(IDENT, ID) @@ -109,10 +218,8 @@ func (p *parser) parseOrder() (mul []*Order, err error) { return nil, err } - tok, lit, exi = p.mightBe(ASC, DESC) - if !exi { + if tok, lit, exi = p.mightBe(ASC, DESC); !exi { tok = ASC - lit = tok.String() } one.Dir, err = p.declare(tok, lit) @@ -120,9 +227,15 @@ func (p *parser) parseOrder() (mul []*Order, err error) { return nil, err } + // Append the single expression to the array + // of return statement expressions. + 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 { break } @@ -135,10 +248,18 @@ func (p *parser) parseOrder() (mul []*Order, err 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 { 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) tok, lit, err := p.shouldBe(NUMBER) @@ -152,17 +273,23 @@ func (p *parser) parseLimit() (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 { 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) - tok, lit, err := p.shouldBe(NUMBER, THING) + tok, lit, err := p.shouldBe(NUMBER) 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) @@ -171,7 +298,6 @@ func (p *parser) parseStart() (Expr, error) { func (p *parser) parseVersion() (Expr, error) { - // Remove the VERSION keyword if _, _, exi := p.mightBe(VERSION, ON); !exi { return nil, nil } diff --git a/sql/util.go b/sql/util.go index 6b311a47..2a0c5c6c 100644 --- a/sql/util.go +++ b/sql/util.go @@ -90,9 +90,12 @@ func (p *parser) declare(tok Token, lit string) (interface{}, error) { case EMPTY: return &Empty{}, nil - case ALL: + case MUL: return &All{}, nil + case QMARK: + return &Any{}, nil + case ASC: return &Asc{}, nil diff --git a/sql/view.go b/sql/view.go index 746fedd2..77cacc28 100644 --- a/sql/view.go +++ b/sql/view.go @@ -36,7 +36,7 @@ func (p *parser) parseDefineViewStatement() (stmt *DefineViewStatement, err erro return nil, err } - if stmt.Expr, err = p.parseExpr(); err != nil { + if stmt.Expr, err = p.parseField(); err != nil { return nil, err } diff --git a/util/item/check.go b/util/item/check.go index 4cccf1a9..fb136327 100644 --- a/util/item/check.go +++ b/util/item/check.go @@ -24,14 +24,12 @@ import ( "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 := part.(type) { - case *sql.BinaryExpression: - if !this.chkOne(expr) { - return false - } + switch expr := cond.(type) { + case *sql.BinaryExpression: + if !this.chkOne(expr) { + return false } } diff --git a/util/item/merge.go b/util/item/merge.go index 87e4322e..19170156 100644 --- a/util/item/merge.go +++ b/util/item/merge.go @@ -44,7 +44,7 @@ func (this *Doc) Merge(data []sql.Expr) (err error) { switch expr := part.(type) { case *sql.DiffExpression: this.mrgDpm(expr) - case *sql.BinaryExpression: + case *sql.DataExpression: this.mrgOne(expr) case *sql.MergeExpression: 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) rhs := this.getMrgItemRHS(expr.RHS)