Add subqueries, subexpressions, and paths to SQL
This commit is contained in:
parent
28d1806785
commit
68c22bc25c
17 changed files with 760 additions and 227 deletions
|
@ -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
|
||||||
|
|
14
db/return.go
14
db/return.go
|
@ -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
|
||||||
|
|
84
sql/ast.go
84
sql/ast.go
|
@ -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{}
|
||||||
}
|
}
|
||||||
|
|
93
sql/cond.go
93
sql/cond.go
|
@ -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
|
|
||||||
|
|
||||||
}
|
|
38
sql/data.go
38
sql/data.go
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
511
sql/exprs.go
511
sql/exprs.go
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
sql/let.go
19
sql/let.go
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
150
sql/select.go
150
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) {
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue