Improvements on sql parser

This commit is contained in:
Tobie Morgan Hitchcock 2016-09-06 14:30:59 +01:00
parent d76038652b
commit 08943946a9
17 changed files with 1447 additions and 733 deletions

View file

@ -30,20 +30,45 @@ type Statement interface{}
type Statements []Statement type Statements []Statement
// -------------------------------------------------- // --------------------------------------------------
// Select // Use
// -------------------------------------------------- // --------------------------------------------------
// UseStatement represents a SQL USE statement. // UseStatement represents a SQL USE statement.
type UseStatement struct { type UseStatement struct {
NS string // Namespace NS string // Namespace
DB string // Database DB string // Database
CK string // Cipherkey
} }
// -------------------------------------------------- // --------------------------------------------------
// Select // Trans
// -------------------------------------------------- // --------------------------------------------------
type BeginStatement struct{}
type CancelStatement struct{}
type CommitStatement struct{}
// --------------------------------------------------
// Normal
// --------------------------------------------------
// ActionStatement represents a SQL ACTION statement.
type ActionStatement struct {
EX bool // Explain
KV string // Bucket
NS string // Namespace
DB string // Database
Expr []*Field // Which fields
What []Expr // What to select
Cond []Expr // Select conditions
Group []*Group // Group by
Order []*Order // Order by
Limit Expr // Limit by
Start Expr // Start at
Version Expr // Version
}
// SelectStatement represents a SQL SELECT statement. // SelectStatement represents a SQL SELECT statement.
type SelectStatement struct { type SelectStatement struct {
EX bool // Explain EX bool // Explain
@ -58,12 +83,9 @@ type SelectStatement struct {
Limit Expr // Limit by Limit Expr // Limit by
Start Expr // Start at Start Expr // Start at
Version Expr // Version Version Expr // Version
Echo Token // What to return
} }
// --------------------------------------------------
// Items
// --------------------------------------------------
// CreateStatement represents a SQL CREATE statement. // CreateStatement represents a SQL CREATE statement.
// //
// CREATE person SET column = 'value' RETURN ID // CREATE person SET column = 'value' RETURN ID
@ -157,22 +179,26 @@ type RecordStatement struct {
// //
// DEFINE RULES person // DEFINE RULES person
type DefineRulesStatement struct { type DefineRulesStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
When []string `json:"-" msgpack:"-"` // Action names
Rule string `json:"rule" msgpack:"rule"` // Rule behaviour
Code string `json:"code" msgpack:"code"` // Rule custom code
} }
// RemoveRulesStatement represents an SQL REMOVE RULES statement. // RemoveRulesStatement represents an SQL REMOVE RULES statement.
// //
// REMOVE RULES person // REMOVE RULES person
type RemoveRulesStatement struct { type RemoveRulesStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
When []string `json:"-" msgpack:"-"` // Action names
} }
// -------------------------------------------------- // --------------------------------------------------
@ -183,22 +209,22 @@ type RemoveRulesStatement struct {
// //
// DEFINE TABLE person // DEFINE TABLE person
type DefineTableStatement struct { type DefineTableStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
} }
// RemoveTableStatement represents an SQL REMOVE TABLE statement. // RemoveTableStatement represents an SQL REMOVE TABLE statement.
// //
// REMOVE TABLE person // REMOVE TABLE person
type RemoveTableStatement struct { type RemoveTableStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
} }
// -------------------------------------------------- // --------------------------------------------------
@ -211,35 +237,35 @@ type RemoveTableStatement struct {
// DEFINE FIELD name ON person TYPE number MIN 0 MAX 5 DEFAULT 0 // DEFINE FIELD name ON person TYPE number MIN 0 MAX 5 DEFAULT 0
// DEFINE FIELD name ON person TYPE custom ENUM [0,1,2,3,4,5] DEFAULT 0 // DEFINE FIELD name ON person TYPE custom ENUM [0,1,2,3,4,5] DEFAULT 0
type DefineFieldStatement struct { type DefineFieldStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
Name Ident // Field name Name string `json:"name" msgpack:"name"` // Field name
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
Type Ident // Field type Type string `json:"type" msgpack:"type"` // Field type
Enum []interface{} // Custom options Enum []interface{} `json:"enum" msgpack:"enum"` // Custom options
Code string // Field code Code string `json:"code" msgpack:"code"` // Field code
Min float64 // Minimum value / length Min float64 `json:"min" msgpack:"min"` // Minimum value / length
Max float64 // Maximum value / length Max float64 `json:"max" msgpack:"max"` // Maximum value / length
Match string // Regex value Match string `json:"match" msgpack:"match"` // Regex value
Default interface{} // Default value Default interface{} `json:"default" msgpack:"default"` // Default value
Notnull bool // Notnull - can not be NULL? Notnull bool `json:"notnull" msgpack:"notnull"` // Notnull - can not be NULL?
Readonly bool // Readonly - can not be changed? Readonly bool `json:"readonly" msgpack:"readonly"` // Readonly - can not be changed?
Mandatory bool // Mandatory - can not be VOID? Mandatory bool `json:"mandatory" msgpack:"mandatory"` // Mandatory - can not be VOID?
Validate bool // Validate - can not be INCORRECT? Validate bool `json:"validate" msgpack:"validate"` // Validate - can not be INCORRECT?
} }
// RemoveFieldStatement represents an SQL REMOVE INDEX statement. // RemoveFieldStatement represents an SQL REMOVE INDEX statement.
// //
// REMOVE FIELD name ON person // REMOVE FIELD name ON person
type RemoveFieldStatement struct { type RemoveFieldStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
Name Ident // Field name Name string `json:"-" msgpack:"-"` // Field name
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
} }
// -------------------------------------------------- // --------------------------------------------------
@ -250,38 +276,39 @@ type RemoveFieldStatement struct {
// //
// DEFINE INDEX name ON person COLUMNS (account, age) UNIQUE // DEFINE INDEX name ON person COLUMNS (account, age) UNIQUE
type DefineIndexStatement struct { type DefineIndexStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
Name Ident // Index name Name string `json:"name" msgpack:"name"` // Index name
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
Code string // Index code Cols []string `json:"cols" msgpack:"cols"` // Index cols
Cols []*Field // Index cols Uniq bool `json:"unique" msgpack:"unique"` // Unique index
Uniq bool // Unique index CI bool
CS bool
} }
// RemoveIndexStatement represents an SQL REMOVE INDEX statement. // RemoveIndexStatement represents an SQL REMOVE INDEX statement.
// //
// REMOVE INDEX name ON person // REMOVE INDEX name ON person
type RemoveIndexStatement struct { type RemoveIndexStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
Name Ident // Index name Name string `json:"-" msgpack:"-"` // Index name
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
} }
// ResyncIndexStatement represents an SQL RESYNC INDEX statement. // ResyncIndexStatement represents an SQL RESYNC INDEX statement.
// //
// RESYNC INDEX name ON person // RESYNC INDEX name ON person
type ResyncIndexStatement struct { type ResyncIndexStatement struct {
EX bool // Explain EX bool `json:"-" msgpack:"-"` // Explain
KV string // Bucket KV string `json:"-" msgpack:"-"` // Bucket
NS string // Namespace NS string `json:"-" msgpack:"-"` // Namespace
DB string // Database DB string `json:"-" msgpack:"-"` // Database
What []Table // Table names What []string `json:"-" msgpack:"-"` // Table names
} }
// -------------------------------------------------- // --------------------------------------------------
@ -309,9 +336,6 @@ type Void struct{}
// Empty represents an expression which is null or "". // Empty represents an expression which is null or "".
type Empty struct{} type Empty struct{}
// Wildcard represents a wildcard expression.
type Wildcard struct{}
// ClosedExpression represents a parenthesized expression. // ClosedExpression represents a parenthesized expression.
type ClosedExpression struct { type ClosedExpression struct {
Expr Expr Expr Expr
@ -320,7 +344,7 @@ type ClosedExpression struct {
// BinaryExpression represents a binary expression tree, // BinaryExpression represents a binary expression tree,
type BinaryExpression struct { type BinaryExpression struct {
LHS Expr LHS Expr
Op string Op Token
RHS Expr RHS Expr
} }
@ -343,23 +367,14 @@ type ContentExpression struct {
// Parts // Parts
// -------------------------------------------------- // --------------------------------------------------
type Table string // Ident comment
type Ident struct {
func (this Table) String() string { ID string
return string(this)
} }
func (this Table) MarshalText() ([]byte, error) { // Table comment
return []byte(string(this)), nil type Table struct {
} TB string
type Ident string
func (this Ident) String() string {
return string(this)
}
func (this Ident) MarshalText() ([]byte, error) {
return []byte(string(this)), nil
} }
// Thing comment // Thing comment
@ -371,15 +386,15 @@ type Thing struct {
// Field comment // Field comment
type Field struct { type Field struct {
Expr Expr Expr Expr
Alias Expr Alias string
} }
// Group comment // Group represents an sql GROUP BY clause
type Group struct { type Group struct {
Expr Expr Expr Expr
} }
// Order comment // Order represents an sql ORDER BY clause
type Order struct { type Order struct {
Expr Expr Expr Expr
Dir Expr Dir Expr

View file

@ -28,7 +28,7 @@ func (p *Parser) parseCond() (mul []Expr, err error) {
one := &BinaryExpression{} one := &BinaryExpression{}
tok, lit, err = p.shouldBe(IDENT, ID, TIME, TRUE, FALSE, STRING, NUMBER, DOUBLE) tok, lit, err = p.shouldBe(ID, IDENT, NULL, VOID, MISSING, EMPTY, NOW, DATE, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, REGEX, JSON, ARRAY, BOUNDPARAM)
if err != nil { if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}} return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
} }
@ -38,13 +38,39 @@ func (p *Parser) parseCond() (mul []Expr, err error) {
return nil, err return nil, err
} }
tok, lit, err = p.shouldBe(IN, EQ, NEQ, GT, LT, GTE, LTE, EQR, NER, SEQ, SNE) 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 { if err != nil {
return nil, err return nil, err
} }
one.Op = lit one.Op = tok
tok, lit, err = p.shouldBe(IDENT, ID, NULL, EMPTY, NOW, DATE, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, REGEX, JSON, ARRAY) 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, NULL, VOID, MISSING, EMPTY, NOW, DATE, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, REGEX, JSON, ARRAY, BOUNDPARAM)
if err != nil { if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field value"}} return nil, &ParseError{Found: lit, Expected: []string{"field value"}}
} }
@ -56,7 +82,6 @@ func (p *Parser) parseCond() (mul []Expr, err error) {
mul = append(mul, one) mul = append(mul, one)
// Remove the WHERE keyword
if _, _, exi := p.mightBe(AND, OR); !exi { if _, _, exi := p.mightBe(AND, OR); !exi {
break break
} }

View file

@ -65,9 +65,9 @@ func (p *Parser) parseSet() (mul []Expr, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
one.Op = lit one.Op = tok
tok, lit, err = p.shouldBe(IDENT, NULL, VOID, NOW, DATE, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, JSON, ARRAY) tok, lit, err = p.shouldBe(IDENT, NULL, VOID, NOW, DATE, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, JSON, ARRAY, BOUNDPARAM)
if err != nil { if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field value"}} return nil, &ParseError{Found: lit, Expected: []string{"field value"}}
} }
@ -94,7 +94,7 @@ func (p *Parser) parseDiff() (exp []Expr, err error) {
one := &DiffExpression{} one := &DiffExpression{}
tok, lit, err := p.shouldBe(JSON) tok, lit, err := p.shouldBe(JSON, ARRAY)
if err != nil { if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"json"}} return nil, &ParseError{Found: lit, Expected: []string{"json"}}
} }

View file

@ -16,26 +16,59 @@ package sql
import ( import (
"regexp" "regexp"
"strconv"
"time"
) )
func (p *Parser) parseName() (string, error) {
_, lit, err := p.shouldBe(IDENT)
if err != nil {
return string(""), &ParseError{Found: lit, Expected: []string{"name"}}
}
val, err := declare(STRING, lit)
return val.(string), err
}
func (p *Parser) parseNames() (mul []string, err error) {
for {
one, err := p.parseName()
if err != nil {
return nil, err
}
mul = append(mul, one)
// If the next token is not a comma then break the loop.
if _, _, exi := p.mightBe(COMMA); !exi {
break
}
}
return
}
// -------------------------------------------------- // --------------------------------------------------
// //
// -------------------------------------------------- // --------------------------------------------------
func (p *Parser) parseTable() (Table, error) { func (p *Parser) parseTable() (*Table, error) {
_, lit, err := p.shouldBe(IDENT, NUMBER, DATE) _, lit, err := p.shouldBe(IDENT, NUMBER, DATE)
if err != nil { if err != nil {
return Table(""), &ParseError{Found: lit, Expected: []string{"table name"}} return nil, &ParseError{Found: lit, Expected: []string{"table name"}}
} }
return Table(lit), err return &Table{lit}, err
} }
func (p *Parser) parseTables() (mul []Table, err error) { func (p *Parser) parseTables() (mul []*Table, err error) {
for { for {
@ -92,14 +125,8 @@ func (p *Parser) parseThing() (one *Thing, err error) {
switch tok { switch tok {
case IDENT: case IDENT:
val = lit val = lit
case NUMBER: default:
val, err = strconv.ParseInt(lit, 10, 64) val, err = declare(tok, lit)
case DOUBLE:
val, err = strconv.ParseFloat(lit, 64)
case DATE:
val, err = time.Parse("2006-01-02", lit)
case TIME:
val, err = time.Parse(time.RFC3339, lit)
} }
if err != nil { if err != nil {
@ -138,16 +165,16 @@ func (p *Parser) parseThings() (mul []Expr, err error) {
// //
// -------------------------------------------------- // --------------------------------------------------
func (p *Parser) parseIdent() (Ident, error) { func (p *Parser) parseIdent() (*Ident, error) {
_, lit, err := p.shouldBe(IDENT) _, lit, err := p.shouldBe(IDENT)
if err != nil { if err != nil {
return Ident(""), &ParseError{Found: lit, Expected: []string{"name"}} return nil, &ParseError{Found: lit, Expected: []string{"name"}}
} }
val, err := declare(IDENT, lit) val, err := declare(IDENT, lit)
return val.(Ident), err return val.(*Ident), err
} }
@ -220,7 +247,7 @@ func (p *Parser) parseScript() (string, error) {
tok, lit, err := p.shouldBe(STRING, REGION) tok, lit, err := p.shouldBe(STRING, REGION)
if err != nil { if err != nil {
return string(""), &ParseError{Found: lit, Expected: []string{"LUA script"}} return string(""), &ParseError{Found: lit, Expected: []string{"js/lua script"}}
} }
val, err := declare(tok, lit) val, err := declare(tok, lit)
@ -257,7 +284,7 @@ func (p *Parser) parseBoolean() (bool, error) {
func (p *Parser) parseDefault() (interface{}, error) { func (p *Parser) parseDefault() (interface{}, error) {
tok, lit, err := p.shouldBe(NULL, NOW, DATE, TIME, TRUE, FALSE, NUMBER, STRING, REGION, ARRAY, JSON) tok, lit, err := p.shouldBe(NULL, NOW, DATE, TIME, TRUE, FALSE, NUMBER, DOUBLE, STRING, REGION, IDENT, ARRAY, JSON)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -271,12 +298,13 @@ func (p *Parser) parseExpr() (mul []*Field, err error) {
var tok Token var tok Token
var lit string var lit string
var exi bool var exi bool
var val interface{}
for { for {
one := &Field{} one := &Field{}
tok, lit, err = p.shouldBe(IDENT, ID, OEDGE, IEDGE, BEDGE, NULL, ALL, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, JSON) tok, lit, err = p.shouldBe(IDENT, ID, NOW, PATH, NULL, ALL, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, JSON, ARRAY)
if err != nil { if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}} return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
} }
@ -286,19 +314,23 @@ func (p *Parser) parseExpr() (mul []*Field, err error) {
return return
} }
one.Alias = lit
// Next token might be AS // Next token might be AS
if _, _, exi = p.mightBe(AS); exi { if _, _, exi = p.mightBe(AS); exi {
tok, lit, err = p.shouldBe(IDENT) _, lit, err = p.shouldBe(IDENT)
if err != nil { if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field alias"}} return nil, &ParseError{Found: lit, Expected: []string{"field alias"}}
} }
one.Alias, err = declare(tok, lit) val, err = declare(STRING, lit)
if err != nil { if err != nil {
return return
} }
one.Alias = val.(string)
} }
mul = append(mul, one) mul = append(mul, one)

View file

@ -24,7 +24,7 @@ func (p *Parser) parseDefineFieldStatement(explain bool) (stmt *DefineFieldState
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)
if stmt.Name, err = p.parseIdent(); err != nil { if stmt.Name, err = p.parseName(); err != nil {
return nil, err return nil, err
} }
@ -32,7 +32,7 @@ func (p *Parser) parseDefineFieldStatement(explain bool) (stmt *DefineFieldState
return nil, err return nil, err
} }
if stmt.What, err = p.parseTables(); err != nil { if stmt.What, err = p.parseNames(); err != nil {
return nil, err return nil, err
} }
@ -145,7 +145,7 @@ func (p *Parser) parseRemoveFieldStatement(explain bool) (stmt *RemoveFieldState
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)
if stmt.Name, err = p.parseIdent(); err != nil { if stmt.Name, err = p.parseName(); err != nil {
return nil, err return nil, err
} }
@ -153,7 +153,7 @@ func (p *Parser) parseRemoveFieldStatement(explain bool) (stmt *RemoveFieldState
return nil, err return nil, err
} }
if stmt.What, err = p.parseTables(); err != nil { if stmt.What, err = p.parseNames(); err != nil {
return nil, err return nil, err
} }

View file

@ -24,7 +24,7 @@ func (p *Parser) parseDefineIndexStatement(explain bool) (stmt *DefineIndexState
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)
if stmt.Name, err = p.parseIdent(); err != nil { if stmt.Name, err = p.parseName(); err != nil {
return nil, err return nil, err
} }
@ -32,29 +32,17 @@ func (p *Parser) parseDefineIndexStatement(explain bool) (stmt *DefineIndexState
return nil, err return nil, err
} }
if stmt.What, err = p.parseTables(); err != nil { if stmt.What, err = p.parseNames(); err != nil {
return nil, err return nil, err
} }
if tok, _, err := p.shouldBe(CODE, COLUMNS); tok != 0 { if _, _, err = p.shouldBe(COLUMNS); err != nil {
if err != nil {
return nil, err return nil, err
} }
if is(tok, CODE) { if stmt.Cols, err = p.parseNames(); err != nil {
if stmt.Code, err = p.parseScript(); err != nil {
return nil, err return nil, err
} }
}
if is(tok, COLUMNS) {
if stmt.Cols, err = p.parseExpr(); err != nil {
return nil, err
}
}
}
_, _, stmt.Uniq = p.mightBe(UNIQUE) _, _, stmt.Uniq = p.mightBe(UNIQUE)
@ -80,7 +68,7 @@ func (p *Parser) parseResyncIndexStatement(explain bool) (stmt *ResyncIndexState
return nil, err return nil, err
} }
if stmt.What, err = p.parseTables(); err != nil { if stmt.What, err = p.parseNames(); err != nil {
return nil, err return nil, err
} }
@ -102,7 +90,7 @@ func (p *Parser) parseRemoveIndexStatement(explain bool) (stmt *RemoveIndexState
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)
if stmt.Name, err = p.parseIdent(); err != nil { if stmt.Name, err = p.parseName(); err != nil {
return nil, err return nil, err
} }
@ -110,7 +98,7 @@ func (p *Parser) parseRemoveIndexStatement(explain bool) (stmt *RemoveIndexState
return nil, err return nil, err
} }
if stmt.What, err = p.parseTables(); err != nil { if stmt.What, err = p.parseNames(); err != nil {
return nil, err return nil, err
} }

View file

@ -36,6 +36,10 @@ func (p *Parser) parseModifyStatement(explain bool) (stmt *ModifyStatement, err
return nil, err return nil, err
} }
if stmt.Cond, err = p.parseCond(); err != nil {
return nil, err
}
if stmt.Echo, err = p.parseEcho(); err != nil { if stmt.Echo, err = p.parseEcho(); err != nil {
return nil, err return nil, err
} }

View file

@ -111,13 +111,20 @@ func (p *Parser) ParseSingle() (Statement, error) {
explain = true explain = true
} }
tok, _, err := p.shouldBe(USE, SELECT, CREATE, UPDATE, INSERT, UPSERT, MODIFY, DELETE, RELATE, RECORD, DEFINE, RESYNC, REMOVE) tok, _, err := p.shouldBe(USE, LET, BEGIN, CANCEL, COMMIT, ROLLBACK, SELECT, CREATE, UPDATE, INSERT, UPSERT, MODIFY, DELETE, RELATE, RECORD, DEFINE, RESYNC, REMOVE)
switch tok { switch tok {
case USE: case USE:
return p.parseUseStatement(explain) return p.parseUseStatement(explain)
case BEGIN:
return p.parseBeginStatement(explain)
case CANCEL, ROLLBACK:
return p.parseCancelStatement(explain)
case COMMIT:
return p.parseCommitStatement(explain)
case SELECT: case SELECT:
return p.parseSelectStatement(explain) return p.parseSelectStatement(explain)
case CREATE, INSERT: case CREATE, INSERT:

View file

@ -24,10 +24,51 @@ func (p *Parser) parseDefineRulesStatement(explain bool) (stmt *DefineRulesState
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)
if stmt.What, err = p.parseTables(); err != nil { if _, _, err = p.shouldBe(ON); err != nil {
return nil, err return nil, err
} }
if stmt.What, err = p.parseNames(); err != nil {
return nil, err
}
if _, _, err = p.shouldBe(FOR); err != nil {
return nil, err
}
for {
tok, _, exi := p.mightBe(SELECT, CREATE, UPDATE, DELETE, RELATE, RECORD)
if !exi {
break
}
stmt.When = append(stmt.When, tok.String())
if _, _, exi := p.mightBe(COMMA); !exi {
break
}
}
if len(stmt.When) == 0 {
return nil, &ParseError{Found: "", Expected: []string{"SELECT", "CREATE", "UPDATE", "DELETE", "RELATE", "RECORD"}}
}
if tok, _, err := p.shouldBe(ACCEPT, REJECT, CUSTOM); err != nil {
return nil, err
} else {
stmt.Rule = tok.String()
if is(tok, CUSTOM) {
if stmt.Code, err = p.parseScript(); err != nil {
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
} }
@ -46,10 +87,37 @@ func (p *Parser) parseRemoveRulesStatement(explain bool) (stmt *RemoveRulesState
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)
if stmt.What, err = p.parseTables(); err != nil { if _, _, err = p.shouldBe(ON); err != nil {
return nil, err return nil, err
} }
if stmt.What, err = p.parseNames(); err != nil {
return nil, err
}
if _, _, err = p.shouldBe(FOR); err != nil {
return nil, err
}
for {
tok, _, exi := p.mightBe(SELECT, CREATE, UPDATE, DELETE, RELATE, RECORD)
if !exi {
break
}
stmt.When = append(stmt.When, tok.String())
if _, _, exi := p.mightBe(COMMA); !exi {
break
}
}
if len(stmt.When) == 0 {
return nil, &ParseError{Found: "", Expected: []string{"SELECT", "CREATE", "UPDATE", "DELETE", "RELATE", "RECORD"}}
}
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil { if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err return nil, err
} }

View file

@ -18,12 +18,15 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"regexp"
"strings" "strings"
"time" "time"
) )
// Scanner represents a lexical scanner. // Scanner represents a lexical scanner.
type Scanner struct { type Scanner struct {
p []rune
n []rune
r *bufio.Reader r *bufio.Reader
} }
@ -36,24 +39,21 @@ func NewScanner(r io.Reader) *Scanner {
func (s *Scanner) Scan() (tok Token, lit string) { func (s *Scanner) Scan() (tok Token, lit string) {
// Read the next rune. // Read the next rune.
ch := s.read() ch := s.next()
// If we see whitespace then consume all contiguous whitespace. // If we see whitespace then consume all contiguous whitespace.
if isWhitespace(ch) { if isWhitespace(ch) {
s.unread() return s.scanBlank(ch)
return s.scanWhitespace()
} }
// If we see a letter then consume as an string. // If we see a letter then consume as an string.
if isLetter(ch) { if isLetter(ch) {
s.unread() return s.scanIdent(ch)
return s.scanIdent()
} }
// If we see a number then consume as a number. // If we see a number then consume as a number.
if isNumber(ch) { if isNumber(ch) {
s.unread() return s.scanNumber(ch)
return s.scanNumber()
} }
// Otherwise read the individual character. // Otherwise read the individual character.
@ -63,6 +63,12 @@ func (s *Scanner) Scan() (tok Token, lit string) {
return EOF, "" return EOF, ""
case '*': case '*':
return ALL, string(ch) return ALL, string(ch)
case '×':
return MUL, string(ch)
case '∙':
return MUL, string(ch)
case '÷':
return DIV, string(ch)
case '@': case '@':
return EAT, string(ch) return EAT, string(ch)
case ',': case ',':
@ -70,23 +76,19 @@ func (s *Scanner) Scan() (tok Token, lit string) {
case '.': case '.':
return DOT, string(ch) return DOT, string(ch)
case '"': case '"':
s.unread() return s.scanString(ch)
return s.scanString()
case '\'': case '\'':
s.unread() return s.scanString(ch)
return s.scanString()
case '`': case '`':
s.unread() return s.scanQuoted(ch)
return s.scanQuoted()
case '⟨': case '⟨':
s.unread() return s.scanQuoted(ch)
return s.scanQuoted()
case '{': case '{':
s.unread() return s.scanObject(ch)
return s.scanObject()
case '[': case '[':
s.unread() return s.scanObject(ch)
return s.scanObject() case '$':
return s.scanParams(ch)
case ':': case ':':
return COLON, string(ch) return COLON, string(ch)
case ';': case ';':
@ -95,102 +97,262 @@ func (s *Scanner) Scan() (tok Token, lit string) {
return LPAREN, string(ch) return LPAREN, string(ch)
case ')': case ')':
return RPAREN, string(ch) return RPAREN, string(ch)
case '=': case '¬':
return EQ, string(ch) return NEQ, string(ch)
case '+': case '≤':
if chn := s.read(); chn == '=' { return LTE, string(ch)
return INC, "+=" case '≥':
} return GTE, string(ch)
s.unread() case '~':
return ADD, string(ch) return SIN, string(ch)
case '-': case '∋':
if chn := s.read(); chn == '>' { return SIN, string(ch)
return OEDGE, "->" case '∌':
return SNI, string(ch)
case '⊇':
return CONTAINSALL, string(ch)
case '⊃':
return CONTAINSSOME, string(ch)
case '⊅':
return CONTAINSNONE, string(ch)
case '∈':
return INS, string(ch)
case '∉':
return NIS, string(ch)
case '⊆':
return ALLCONTAINEDIN, string(ch)
case '⊂':
return SOMECONTAINEDIN, string(ch)
case '⊄':
return NONECONTAINEDIN, string(ch)
case '#':
return s.scanCommentSingle(ch)
case '/': case '/':
chn := s.next() chn := s.next()
switch { switch {
case chn == '*': case chn == '*':
return s.scanCommentMultiple(ch) return s.scanCommentMultiple(ch)
case chn == ' ':
s.undo()
return DIV, string(ch)
default: default:
s.unread() s.undo()
return s.scanRegexp(ch) return s.scanRegexp(ch)
} }
s.unread() case '=':
if chn := s.read(); chn == '=' { chn := s.next()
return DEC, "-=" switch {
case chn == '~':
return SIN, "=~"
case chn == '=':
return EEQ, "=="
default:
s.undo()
return EQ, string(ch)
}
case '?':
chn := s.next()
switch {
case chn == '=':
return ANY, "?="
default:
s.undo()
return QMARK, string(ch)
} }
s.unread()
return SUB, string(ch)
case '!': case '!':
if chn := s.read(); chn == '=' { chn := s.next()
switch {
case chn == '=':
if s.next() == '=' {
return NEE, "!=="
} else {
s.undo()
return NEQ, "!=" return NEQ, "!="
} }
s.unread() case chn == '~':
case '<': return SNI, "!~"
if chn := s.read(); chn == '-' { default:
if chn := s.read(); chn == '>' { s.undo()
return BEDGE, "<->" return EXC, string(ch)
} }
s.unread() case '+':
chn := s.next()
switch {
case chn == '=':
return INC, "+="
case isNumber(chn):
return s.scanNumber(ch, chn)
default:
s.undo()
return ADD, string(ch)
}
case '-':
chn := s.next()
switch {
case chn == '=':
return DEC, "-="
case chn == '>':
return OEDGE, "->"
case chn == '-':
return s.scanCommentSingle(ch)
case isNumber(chn):
return s.scanNumber(ch, chn)
default:
s.undo()
return SUB, string(ch)
}
case '>':
chn := s.next()
switch {
case chn == '=':
return GTE, ">="
default:
s.undo()
return GT, string(ch)
}
case '<':
chn := s.next()
switch {
case chn == '>':
return NEQ, "<>"
case chn == '=':
return LTE, "<="
case chn == '-':
if s.next() == '>' {
return BEDGE, "<->"
} else {
s.undo()
return IEDGE, "<-" return IEDGE, "<-"
} }
s.unread() default:
if chn := s.read(); chn == '=' { s.undo()
return LTE, "<="
}
s.unread()
return LT, string(ch) return LT, string(ch)
case '>':
if chn := s.read(); chn == '=' {
return GTE, ">="
} }
s.unread()
return GT, string(ch)
} }
return ILLEGAL, string(ch) return ILLEGAL, string(ch)
} }
// scanWhitespace consumes the current rune and all contiguous whitespace. // scanBlank consumes the current rune and all contiguous whitespace.
func (s *Scanner) scanWhitespace() (tok Token, lit string) { func (s *Scanner) scanBlank(chp ...rune) (tok Token, lit string) {
// Create a buffer and read the current character into it. tok = WS
// Create a buffer
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteRune(s.read())
// Read every subsequent whitespace character into the buffer. // Read passed in runes
// Non-whitespace characters and EOF will cause the loop to exit. for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for { for {
if ch := s.read(); ch == eof { if ch := s.next(); ch == eof {
break break
} else if !isWhitespace(ch) { } else if !isWhitespace(ch) {
s.unread() s.undo()
break break
} else { } else {
buf.WriteRune(ch) buf.WriteRune(ch)
} }
} }
return WS, buf.String() return tok, buf.String()
}
// scanCommentSingle consumes the current rune and all contiguous whitespace.
func (s *Scanner) scanCommentSingle(chp ...rune) (tok Token, lit string) {
tok = WS
// Create a buffer
var buf bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for {
if ch := s.next(); ch == '\n' || ch == '\r' {
buf.WriteRune(ch)
break
} else {
buf.WriteRune(ch)
}
}
return tok, buf.String()
}
// scanCommentMultiple consumes the current rune and all contiguous whitespace.
func (s *Scanner) scanCommentMultiple(chp ...rune) (tok Token, lit string) {
tok = WS
// Create a buffer
var buf bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for {
if ch := s.next(); ch == eof {
break
} else if ch == '*' {
if chn := s.next(); chn == '/' {
buf.WriteRune(chn)
break
}
buf.WriteRune(ch)
} else {
buf.WriteRune(ch)
}
}
return tok, buf.String()
}
func (s *Scanner) scanParams(chp ...rune) (Token, string) {
tok, lit := s.scanIdent(chp...)
if is(tok, IDENT) {
return BOUNDPARAM, lit
}
return tok, lit
} }
// scanIdent consumes the current rune and all contiguous ident runes. // scanIdent consumes the current rune and all contiguous ident runes.
func (s *Scanner) scanIdent() (tok Token, lit string) { func (s *Scanner) scanIdent(chp ...rune) (tok Token, lit string) {
// Create a buffer and read the current character into it. tok = IDENT
// Create a buffer
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteRune(s.read())
// Read every subsequent ident character into the buffer. // Read passed in runes
// Non-ident characters and EOF will cause the loop to exit. for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for { for {
if ch := s.read(); ch == eof { if ch := s.next(); ch == eof {
break break
} else if !isIdentChar(ch) { } else if !isIdentChar(ch) {
s.unread() s.undo()
break break
} else { } else {
buf.WriteRune(ch) buf.WriteRune(ch)
@ -207,22 +369,25 @@ func (s *Scanner) scanIdent() (tok Token, lit string) {
} }
// Otherwise return as a regular identifier. // Otherwise return as a regular identifier.
return IDENT, buf.String() return tok, buf.String()
} }
func (s *Scanner) scanNumber() (tok Token, lit string) { func (s *Scanner) scanNumber(chp ...rune) (tok Token, lit string) {
tok = NUMBER tok = NUMBER
// Create a buffer and read the current character into it. // Create a buffer
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteRune(s.read())
// Read every subsequent ident character into the buffer. // Read passed in runes
// Non-ident characters and EOF will cause the loop to exit. for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for { for {
if ch := s.read(); ch == eof { if ch := s.next(); ch == eof {
break break
} else if isNumber(ch) { } else if isNumber(ch) {
buf.WriteRune(ch) buf.WriteRune(ch)
@ -238,7 +403,7 @@ func (s *Scanner) scanNumber() (tok Token, lit string) {
} }
buf.WriteRune(ch) buf.WriteRune(ch)
} else { } else {
s.unread() s.undo()
break break
} }
} }
@ -247,9 +412,9 @@ func (s *Scanner) scanNumber() (tok Token, lit string) {
} }
func (s *Scanner) scanQuoted() (Token, string) { func (s *Scanner) scanQuoted(chp ...rune) (Token, string) {
tok, lit := s.scanString() tok, lit := s.scanString(chp...)
if is(tok, STRING) { if is(tok, STRING) {
return IDENT, lit return IDENT, lit
@ -259,13 +424,9 @@ func (s *Scanner) scanQuoted() (Token, string) {
} }
func (s *Scanner) scanString() (tok Token, lit string) { func (s *Scanner) scanString(chp ...rune) (tok Token, lit string) {
tok = STRING beg := chp[0]
var buf bytes.Buffer
beg := s.read()
end := beg end := beg
if beg == '"' { if beg == '"' {
@ -280,8 +441,16 @@ func (s *Scanner) scanString() (tok Token, lit string) {
end = '⟩' end = '⟩'
} }
tok = STRING
// Create a buffer
var buf bytes.Buffer
// Ignore passed in runes
// Read subsequent characters
for { for {
if ch := s.read(); ch == end { if ch := s.next(); ch == end {
break break
} else if ch == eof { } else if ch == eof {
return ILLEGAL, buf.String() return ILLEGAL, buf.String()
@ -292,13 +461,13 @@ func (s *Scanner) scanString() (tok Token, lit string) {
tok = REGION tok = REGION
buf.WriteRune(ch) buf.WriteRune(ch)
} else if ch == '\\' { } else if ch == '\\' {
chn := s.read() switch chn := s.next(); chn {
switch chn {
default: default:
buf.WriteRune(chn) buf.WriteRune(chn)
case 'b': case 'b':
continue continue
case 't': case 't':
tok = REGION
buf.WriteRune('\t') buf.WriteRune('\t')
case 'r': case 'r':
tok = REGION tok = REGION
@ -328,13 +497,41 @@ func (s *Scanner) scanString() (tok Token, lit string) {
} }
func (s *Scanner) scanObject() (tok Token, lit string) { func (s *Scanner) scanRegexp(chp ...rune) (tok Token, lit string) {
tok = IDENT tok = IDENT
// Create a buffer
var buf bytes.Buffer var buf bytes.Buffer
beg := s.read() // Ignore passed in runes
// Read subsequent characters
for {
if ch := s.next(); ch == chp[0] {
break
} else if ch == eof {
return ILLEGAL, buf.String()
} else if ch == '\\' {
chn := s.next()
buf.WriteRune(ch)
buf.WriteRune(chn)
} else {
buf.WriteRune(ch)
}
}
if _, err := regexp.Compile(buf.String()); err == nil {
return REGEX, buf.String()
}
return tok, buf.String()
}
func (s *Scanner) scanObject(chp ...rune) (tok Token, lit string) {
beg := chp[0]
end := beg end := beg
sub := 0 sub := 0
@ -346,8 +543,20 @@ func (s *Scanner) scanObject() (tok Token, lit string) {
end = ']' end = ']'
} }
tok = IDENT
// Create a buffer
var buf bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for { for {
if ch := s.read(); ch == end && sub == 0 { if ch := s.next(); ch == end && sub == 0 {
buf.WriteRune(ch)
break break
} else if ch == beg { } else if ch == beg {
sub++ sub++
@ -358,8 +567,7 @@ func (s *Scanner) scanObject() (tok Token, lit string) {
} else if ch == eof { } else if ch == eof {
return ILLEGAL, buf.String() return ILLEGAL, buf.String()
} else if ch == '\\' { } else if ch == '\\' {
chn := s.read() switch chn := s.next(); chn {
switch chn {
default: default:
return ILLEGAL, buf.String() return ILLEGAL, buf.String()
case 'b', 't', 'r', 'n', 'f', '"', '\\': case 'b', 't', 'r', 'n', 'f', '"', '\\':
@ -371,39 +579,49 @@ func (s *Scanner) scanObject() (tok Token, lit string) {
} }
} }
str := buf.String()
str = strings.Replace(str, "\n", "", -1)
str = strings.Replace(str, "\r", "", -1)
str = strings.Trim(str, " ")
if beg == '[' {
return ARRAY, string(beg) + str + string(end)
}
if beg == '{' { if beg == '{' {
if len(str) == 0 || str[0] == '"' { return JSON, buf.String()
return JSON, string(beg) + str + string(end)
} }
if beg == '[' {
return ARRAY, buf.String()
} }
return tok, str return ILLEGAL, buf.String()
} }
// read reads the next rune from the bufferred reader. // next reads the next rune from the bufferred reader.
// Returns the rune(0) if an error occurs (or io.EOF is returned). // Returns the rune(0) if an error occurs (or io.EOF is returned).
func (s *Scanner) read() rune { func (s *Scanner) next() rune {
ch, _, err := s.r.ReadRune()
if len(s.n) > 0 {
var r rune
r, s.n = s.n[len(s.n)-1], s.n[:len(s.n)-1]
s.p = append(s.p, r)
return r
}
r, _, err := s.r.ReadRune()
if err != nil { if err != nil {
return eof return eof
} }
return ch s.p = append(s.p, r)
return r
}
// undo places the previously read rune back on the reader.
func (s *Scanner) undo() {
if len(s.p) > 0 {
var r rune
r, s.p = s.p[len(s.p)-1], s.p[:len(s.p)-1]
s.n = append(s.n, r)
return
} }
// unread places the previously read rune back on the reader.
func (s *Scanner) unread() {
_ = s.r.UnreadRune() _ = s.r.UnreadRune()
} }
// isWhitespace returns true if the rune is a space, tab, or newline. // isWhitespace returns true if the rune is a space, tab, or newline.
@ -411,24 +629,19 @@ func isWhitespace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
} }
// isLetter returns true if the rune is a letter.
func isLetter(ch rune) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
}
// isNumber returns true if the rune is a number. // isNumber returns true if the rune is a number.
func isNumber(ch rune) bool { func isNumber(ch rune) bool {
return (ch >= '0' && ch <= '9') return (ch >= '0' && ch <= '9')
} }
// isSeparator returns true if the rune is a separator expression. // isLetter returns true if the rune is a letter.
func isSeparator(ch rune) bool { func isLetter(ch rune) bool {
return (ch == '.') return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
} }
// isIdentChar returns true if the rune can be used in an unquoted identifier. // 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) || isSeparator(ch) || ch == '_' || ch == '*' || ch == '?' return isLetter(ch) || isNumber(ch) || ch == '.' || ch == '_' || ch == '*'
} }
// eof represents a marker rune for the end of the reader. // eof represents a marker rune for the end of the reader.

View file

@ -61,6 +61,10 @@ func (p *Parser) parseSelectStatement(explain bool) (stmt *SelectStatement, err
return nil, err return nil, err
} }
if stmt.Echo, err = p.parseEcho(); err != nil {
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
} }
@ -113,8 +117,7 @@ func (p *Parser) parseOrder() (mul []*Order, err error) {
tok, lit, exi = p.mightBe(ASC, DESC) tok, lit, exi = p.mightBe(ASC, DESC)
if !exi { if !exi {
tok = ASC tok, lit = ASC, "ASC"
lit = "ASC"
} }
one.Dir, err = declare(tok, lit) one.Dir, err = declare(tok, lit)

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@ func (p *Parser) parseDefineTableStatement(explain bool) (stmt *DefineTableState
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)
if stmt.What, err = p.parseTables(); err != nil { if stmt.What, err = p.parseNames(); err != nil {
return nil, err return nil, err
} }
@ -46,7 +46,7 @@ func (p *Parser) parseRemoveTableStatement(explain bool) (stmt *RemoveTableState
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)
if stmt.What, err = p.parseTables(); err != nil { if stmt.What, err = p.parseNames(); err != nil {
return nil, err return nil, err
} }

View file

@ -31,7 +31,7 @@ const (
DATE // 1970-01-01 DATE // 1970-01-01
TIME // 1970-01-01T00:00:00+00:00 TIME // 1970-01-01T00:00:00+00:00
PATH // :friend PATH // person->like->person
JSON // {"test":true} JSON // {"test":true}
IDENT // something IDENT // something
STRING // "something" STRING // "something"
@ -41,10 +41,12 @@ const (
REGEX // /.*/ REGEX // /.*/
ARRAY // [0,1,2] ARRAY // [0,1,2]
DURATION // 13h DURATION // 13h
BOUNDPARAM // $1
EAT // @ EAT // @
DOT // . DOT // .
COMMA // , COMMA // ,
QMARK // ?
LPAREN // ( LPAREN // (
RPAREN // ) RPAREN // )
LBRACK // [ LBRACK // [
@ -66,15 +68,19 @@ const (
DEC // -= DEC // -=
EQ // = EQ // =
EEQ // ==
EXC // !
NEQ // != NEQ // !=
NEE // !==
ANY // ?=
LT // < LT // <
LTE // <= LTE // <=
GT // > GT // >
GTE // >= GTE // >=
EQR // =~ SIN // ∋
NER // !~ SNI // ∌
SEQ // ∋ INS // ∈
SNE // ∌ NIS // ∉
OEDGE // -> OEDGE // ->
IEDGE // <- IEDGE // <-
@ -89,17 +95,27 @@ const (
ACCEPT ACCEPT
AFTER AFTER
ALL ALL
ALLCONTAINEDIN
AND AND
AS AS
ASC ASC
AT AT
BEFORE BEFORE
BEGIN
BOTH BOTH
BY BY
CANCEL
CODE CODE
COLLECT
COLUMNS COLUMNS
COMMIT
CONTAINS
CONTAINSALL
CONTAINSNONE
CONTAINSSOME
CONTENT CONTENT
CREATE CREATE
CUSTOM
DATABASE DATABASE
DEFAULT DEFAULT
DEFINE DEFINE
@ -113,6 +129,7 @@ const (
EXPUNGE EXPUNGE
FALSE FALSE
FIELD FIELD
FOR
FROM FROM
FULL FULL
GROUP GROUP
@ -122,14 +139,21 @@ const (
INDEX INDEX
INSERT INSERT
INTO INTO
IS
LET
LIMIT LIMIT
MANDATORY MANDATORY
MATCH
MAX MAX
MERGE MERGE
MIN MIN
MISSING
MODIFY MODIFY
MULTI
NAMESPACE NAMESPACE
NONE NONE
NONECONTAINEDIN
NOT
NOTNULL NOTNULL
NOW NOW
NULL NULL
@ -144,17 +168,22 @@ const (
REMOVE REMOVE
RESYNC RESYNC
RETURN RETURN
ROLLBACK
RULES
SELECT SELECT
SET SET
SOMECONTAINEDIN
START START
TABLE TABLE
TO TO
TRANSACTION
TRUE TRUE
TYPE TYPE
UNIQUE UNIQUE
UPDATE UPDATE
UPSERT UPSERT
USE USE
VALIDATE
VERSION VERSION
VOID VOID
WHERE WHERE
@ -182,10 +211,12 @@ var tokens = [...]string{
REGEX: "REGEX", REGEX: "REGEX",
ARRAY: "ARRAY", ARRAY: "ARRAY",
DURATION: "DURATION", DURATION: "DURATION",
BOUNDPARAM: "BOUNDPARAM",
EAT: "@", EAT: "@",
DOT: ".", DOT: ".",
COMMA: ",", COMMA: ",",
QMARK: "?",
LPAREN: "(", LPAREN: "(",
RPAREN: ")", RPAREN: ")",
LBRACK: "[", LBRACK: "[",
@ -203,32 +234,50 @@ var tokens = [...]string{
DEC: "-=", DEC: "-=",
EQ: "=", EQ: "=",
EEQ: "==",
EXC: "!",
NEQ: "!=", NEQ: "!=",
NEE: "!==",
ANY: "?=",
LT: "<", LT: "<",
LTE: "<=", LTE: "<=",
GT: ">", GT: ">",
GTE: ">=", GTE: ">=",
EQR: "=~", SIN: "∋",
NER: "!~", SNI: "∌",
SEQ: "∋", INS: "∈",
SNE: "∌", NIS: "∉",
OEDGE: "->",
IEDGE: "<-",
BEDGE: "<->",
// keywords // keywords
ACCEPT: "ACCEPT", ACCEPT: "ACCEPT",
AFTER: "AFTER", AFTER: "AFTER",
ALL: "ALL", ALL: "ALL",
ALLCONTAINEDIN: "ALLCONTAINEDIN",
AND: "AND", AND: "AND",
AS: "AS", AS: "AS",
ASC: "ASC", ASC: "ASC",
AT: "AT", AT: "AT",
BEFORE: "BEFORE", BEFORE: "BEFORE",
BEGIN: "BEGIN",
BOTH: "BOTH", BOTH: "BOTH",
BY: "BY", BY: "BY",
CANCEL: "CANCEL",
CODE: "CODE", CODE: "CODE",
COLLECT: "COLLECT",
COLUMNS: "COLUMNS", COLUMNS: "COLUMNS",
COMMIT: "COMMIT",
CONTAINS: "CONTAINS",
CONTAINSALL: "CONTAINSALL",
CONTAINSNONE: "CONTAINSNONE",
CONTAINSSOME: "CONTAINSSOME",
CONTENT: "CONTENT", CONTENT: "CONTENT",
CREATE: "CREATE", CREATE: "CREATE",
CUSTOM: "CUSTOM",
DATABASE: "DATABASE", DATABASE: "DATABASE",
DEFAULT: "DEFAULT", DEFAULT: "DEFAULT",
DEFINE: "DEFINE", DEFINE: "DEFINE",
@ -242,6 +291,7 @@ var tokens = [...]string{
EXPUNGE: "EXPUNGE", EXPUNGE: "EXPUNGE",
FALSE: "FALSE", FALSE: "FALSE",
FIELD: "FIELD", FIELD: "FIELD",
FOR: "FOR",
FROM: "FROM", FROM: "FROM",
FULL: "FULL", FULL: "FULL",
GROUP: "GROUP", GROUP: "GROUP",
@ -251,14 +301,21 @@ var tokens = [...]string{
INDEX: "INDEX", INDEX: "INDEX",
INSERT: "INSERT", INSERT: "INSERT",
INTO: "INTO", INTO: "INTO",
IS: "IS",
LET: "LET",
LIMIT: "LIMIT", LIMIT: "LIMIT",
MANDATORY: "MANDATORY", MANDATORY: "MANDATORY",
MATCH: "MATCH",
MAX: "MAX", MAX: "MAX",
MERGE: "MERGE", MERGE: "MERGE",
MIN: "MIN", MIN: "MIN",
MISSING: "MISSING",
MODIFY: "MODIFY", MODIFY: "MODIFY",
MULTI: "MULTI",
NAMESPACE: "NAMESPACE", NAMESPACE: "NAMESPACE",
NONE: "NONE", NONE: "NONE",
NONECONTAINEDIN: "NONECONTAINEDIN",
NOT: "NOT",
NOTNULL: "NOTNULL", NOTNULL: "NOTNULL",
NOW: "NOW", NOW: "NOW",
NULL: "NULL", NULL: "NULL",
@ -272,17 +329,22 @@ var tokens = [...]string{
REMOVE: "REMOVE", REMOVE: "REMOVE",
RESYNC: "RESYNC", RESYNC: "RESYNC",
RETURN: "RETURN", RETURN: "RETURN",
ROLLBACK: "ROLLBACK",
RULES: "RULES",
SELECT: "SELECT", SELECT: "SELECT",
SET: "SET", SET: "SET",
SOMECONTAINEDIN: "SOMECONTAINEDIN",
START: "START", START: "START",
TABLE: "TABLE", TABLE: "TABLE",
TO: "TO", TO: "TO",
TRANSACTION: "TRANSACTION",
TRUE: "TRUE", TRUE: "TRUE",
TYPE: "TYPE", TYPE: "TYPE",
UNIQUE: "UNIQUE", UNIQUE: "UNIQUE",
UPDATE: "UPDATE", UPDATE: "UPDATE",
UPSERT: "UPSERT", UPSERT: "UPSERT",
USE: "USE", USE: "USE",
VALIDATE: "VALIDATE",
VERSION: "VERSION", VERSION: "VERSION",
VOID: "VOID", VOID: "VOID",
WHERE: "WHERE", WHERE: "WHERE",
@ -325,7 +387,11 @@ func (tok Token) precedence() int {
return 1 return 1
case AND: case AND:
return 2 return 2
case EQ, NEQ, EQR, NER, LT, LTE, GT, GTE: case EQ, NEQ, EEQ, NEE,
LT, LTE, GT, GTE,
ANY, SIN, SNI, INS, NIS,
CONTAINSALL, CONTAINSNONE, CONTAINSSOME,
ALLCONTAINEDIN, NONECONTAINEDIN, SOMECONTAINEDIN:
return 3 return 3
case ADD, SUB: case ADD, SUB:
return 4 return 4

57
sql/trans.go Normal file
View file

@ -0,0 +1,57 @@
// 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) parseBeginStatement(explain bool) (stmt *BeginStatement, err error) {
stmt = &BeginStatement{}
_, _, _ = p.mightBe(TRANSACTION)
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return
}
func (p *Parser) parseCancelStatement(explain bool) (stmt *CancelStatement, err error) {
stmt = &CancelStatement{}
_, _, _ = p.mightBe(TRANSACTION)
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return
}
func (p *Parser) parseCommitStatement(explain bool) (stmt *CommitStatement, err error) {
stmt = &CommitStatement{}
_, _, _ = p.mightBe(TRANSACTION)
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return
}

View file

@ -14,21 +14,21 @@
package sql package sql
func (p *Parser) parseType() (exp Ident, err error) { func (p *Parser) parseType() (exp string, err error) {
allowed := []string{"any", "url", "uuid", "color", "email", "phone", "array", "object", "domain", "string", "number", "custom", "boolean", "datetime", "latitude", "longitude"} allowed := []string{"any", "url", "uuid", "color", "email", "phone", "array", "object", "domain", "string", "number", "custom", "boolean", "datetime", "latitude", "longitude"}
tok, lit, err := p.shouldBe(IDENT) _, lit, err := p.shouldBe(IDENT, CUSTOM)
if err != nil { if err != nil {
return Ident(""), err return string(""), &ParseError{Found: lit, Expected: allowed}
} }
if !contains(lit, allowed) { if !contains(lit, allowed) {
return Ident(""), &ParseError{Found: lit, Expected: allowed} return string(""), &ParseError{Found: lit, Expected: allowed}
} }
val, err := declare(tok, lit) val, err := declare(STRING, lit)
return val.(Ident), err return val.(string), err
} }

View file

@ -80,6 +80,9 @@ func declare(tok Token, lit string) (interface{}, error) {
case VOID: case VOID:
return &Void{}, nil return &Void{}, nil
case MISSING:
return &Void{}, nil
case EMPTY: case EMPTY:
return &Empty{}, nil return &Empty{}, nil
@ -93,10 +96,10 @@ func declare(tok Token, lit string) (interface{}, error) {
return &Desc{}, nil return &Desc{}, nil
case ID: case ID:
return Ident(lit), nil return &Ident{lit}, nil
case IDENT: case IDENT:
return Ident(lit), nil return &Ident{lit}, nil
case NOW: case NOW:
return time.Now(), nil return time.Now(), nil
@ -111,7 +114,7 @@ func declare(tok Token, lit string) (interface{}, error) {
return regexp.Compile(lit) return regexp.Compile(lit)
case NUMBER: case NUMBER:
return strconv.ParseInt(lit, 10, 64) return strconv.ParseFloat(lit, 64)
case DOUBLE: case DOUBLE:
return strconv.ParseFloat(lit, 64) return strconv.ParseFloat(lit, 64)
@ -120,7 +123,7 @@ func declare(tok Token, lit string) (interface{}, error) {
return time.ParseDuration(lit) return time.ParseDuration(lit)
case ARRAY: case ARRAY:
var j interface{} var j []interface{}
json.Unmarshal([]byte(lit), &j) json.Unmarshal([]byte(lit), &j)
if j == nil { if j == nil {
return j, fmt.Errorf("Invalid JSON: %s", lit) return j, fmt.Errorf("Invalid JSON: %s", lit)
@ -128,7 +131,7 @@ func declare(tok Token, lit string) (interface{}, error) {
return j, nil return j, nil
case JSON: case JSON:
var j interface{} var j map[string]interface{}
json.Unmarshal([]byte(lit), &j) json.Unmarshal([]byte(lit), &j)
if j == nil { if j == nil {
return j, fmt.Errorf("Invalid JSON: %s", lit) return j, fmt.Errorf("Invalid JSON: %s", lit)