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

View file

@ -28,7 +28,7 @@ func (p *Parser) parseCond() (mul []Expr, err error) {
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 {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
}
@ -38,13 +38,39 @@ func (p *Parser) parseCond() (mul []Expr, err error) {
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 {
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 {
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)
// Remove the WHERE keyword
if _, _, exi := p.mightBe(AND, OR); !exi {
break
}

View file

@ -65,9 +65,9 @@ func (p *Parser) parseSet() (mul []Expr, err error) {
if err != nil {
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 {
return nil, &ParseError{Found: lit, Expected: []string{"field value"}}
}
@ -94,7 +94,7 @@ func (p *Parser) parseDiff() (exp []Expr, err error) {
one := &DiffExpression{}
tok, lit, err := p.shouldBe(JSON)
tok, lit, err := p.shouldBe(JSON, ARRAY)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"json"}}
}

View file

@ -16,26 +16,59 @@ package sql
import (
"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)
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 {
@ -92,14 +125,8 @@ func (p *Parser) parseThing() (one *Thing, err error) {
switch tok {
case IDENT:
val = lit
case NUMBER:
val, err = strconv.ParseInt(lit, 10, 64)
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)
default:
val, err = declare(tok, lit)
}
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)
if err != nil {
return Ident(""), &ParseError{Found: lit, Expected: []string{"name"}}
return nil, &ParseError{Found: lit, Expected: []string{"name"}}
}
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)
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)
@ -257,7 +284,7 @@ func (p *Parser) parseBoolean() (bool, 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 {
return nil, err
}
@ -271,12 +298,13 @@ func (p *Parser) parseExpr() (mul []*Field, err error) {
var tok Token
var lit string
var exi bool
var val interface{}
for {
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 {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
}
@ -286,19 +314,23 @@ func (p *Parser) parseExpr() (mul []*Field, err error) {
return
}
one.Alias = lit
// Next token might be AS
if _, _, exi = p.mightBe(AS); exi {
tok, lit, err = p.shouldBe(IDENT)
_, lit, err = p.shouldBe(IDENT)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field alias"}}
}
one.Alias, err = declare(tok, lit)
val, err = declare(STRING, lit)
if err != nil {
return
}
one.Alias = val.(string)
}
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.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
}
@ -32,7 +32,7 @@ func (p *Parser) parseDefineFieldStatement(explain bool) (stmt *DefineFieldState
return nil, err
}
if stmt.What, err = p.parseTables(); err != nil {
if stmt.What, err = p.parseNames(); err != nil {
return nil, err
}
@ -145,7 +145,7 @@ func (p *Parser) parseRemoveFieldStatement(explain bool) (stmt *RemoveFieldState
stmt.NS = p.c.Get("NS").(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
}
@ -153,7 +153,7 @@ func (p *Parser) parseRemoveFieldStatement(explain bool) (stmt *RemoveFieldState
return nil, err
}
if stmt.What, err = p.parseTables(); err != nil {
if stmt.What, err = p.parseNames(); err != nil {
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.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
}
@ -32,28 +32,16 @@ func (p *Parser) parseDefineIndexStatement(explain bool) (stmt *DefineIndexState
return nil, err
}
if stmt.What, err = p.parseTables(); err != nil {
if stmt.What, err = p.parseNames(); err != nil {
return nil, err
}
if tok, _, err := p.shouldBe(CODE, COLUMNS); tok != 0 {
if err != nil {
return nil, err
}
if is(tok, CODE) {
if stmt.Code, err = p.parseScript(); err != nil {
return nil, err
}
}
if is(tok, COLUMNS) {
if stmt.Cols, err = p.parseExpr(); err != nil {
return nil, err
}
}
if _, _, err = p.shouldBe(COLUMNS); err != nil {
return nil, err
}
if stmt.Cols, err = p.parseNames(); err != nil {
return nil, err
}
_, _, stmt.Uniq = p.mightBe(UNIQUE)
@ -80,7 +68,7 @@ func (p *Parser) parseResyncIndexStatement(explain bool) (stmt *ResyncIndexState
return nil, err
}
if stmt.What, err = p.parseTables(); err != nil {
if stmt.What, err = p.parseNames(); err != nil {
return nil, err
}
@ -102,7 +90,7 @@ func (p *Parser) parseRemoveIndexStatement(explain bool) (stmt *RemoveIndexState
stmt.NS = p.c.Get("NS").(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
}
@ -110,7 +98,7 @@ func (p *Parser) parseRemoveIndexStatement(explain bool) (stmt *RemoveIndexState
return nil, err
}
if stmt.What, err = p.parseTables(); err != nil {
if stmt.What, err = p.parseNames(); err != nil {
return nil, err
}

View file

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

View file

@ -111,13 +111,20 @@ func (p *Parser) ParseSingle() (Statement, error) {
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 {
case USE:
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:
return p.parseSelectStatement(explain)
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.DB = p.c.Get("DB").(string)
if stmt.What, err = p.parseTables(); err != nil {
if _, _, err = p.shouldBe(ON); err != nil {
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 {
return nil, err
}
@ -46,10 +87,37 @@ func (p *Parser) parseRemoveRulesStatement(explain bool) (stmt *RemoveRulesState
stmt.NS = p.c.Get("NS").(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
}
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 {
return nil, err
}

View file

@ -18,12 +18,15 @@ import (
"bufio"
"bytes"
"io"
"regexp"
"strings"
"time"
)
// Scanner represents a lexical scanner.
type Scanner struct {
p []rune
n []rune
r *bufio.Reader
}
@ -36,24 +39,21 @@ func NewScanner(r io.Reader) *Scanner {
func (s *Scanner) Scan() (tok Token, lit string) {
// Read the next rune.
ch := s.read()
ch := s.next()
// If we see whitespace then consume all contiguous whitespace.
if isWhitespace(ch) {
s.unread()
return s.scanWhitespace()
return s.scanBlank(ch)
}
// If we see a letter then consume as an string.
if isLetter(ch) {
s.unread()
return s.scanIdent()
return s.scanIdent(ch)
}
// If we see a number then consume as a number.
if isNumber(ch) {
s.unread()
return s.scanNumber()
return s.scanNumber(ch)
}
// Otherwise read the individual character.
@ -63,6 +63,12 @@ func (s *Scanner) Scan() (tok Token, lit string) {
return EOF, ""
case '*':
return ALL, string(ch)
case '×':
return MUL, string(ch)
case '∙':
return MUL, string(ch)
case '÷':
return DIV, string(ch)
case '@':
return EAT, string(ch)
case ',':
@ -70,23 +76,19 @@ func (s *Scanner) Scan() (tok Token, lit string) {
case '.':
return DOT, string(ch)
case '"':
s.unread()
return s.scanString()
return s.scanString(ch)
case '\'':
s.unread()
return s.scanString()
return s.scanString(ch)
case '`':
s.unread()
return s.scanQuoted()
return s.scanQuoted(ch)
case '⟨':
s.unread()
return s.scanQuoted()
return s.scanQuoted(ch)
case '{':
s.unread()
return s.scanObject()
return s.scanObject(ch)
case '[':
s.unread()
return s.scanObject()
return s.scanObject(ch)
case '$':
return s.scanParams(ch)
case ':':
return COLON, string(ch)
case ';':
@ -95,102 +97,262 @@ func (s *Scanner) Scan() (tok Token, lit string) {
return LPAREN, string(ch)
case ')':
return RPAREN, string(ch)
case '=':
return EQ, string(ch)
case '+':
if chn := s.read(); chn == '=' {
return INC, "+="
}
s.unread()
return ADD, string(ch)
case '-':
if chn := s.read(); chn == '>' {
return OEDGE, "->"
case '¬':
return NEQ, string(ch)
case '≤':
return LTE, string(ch)
case '≥':
return GTE, string(ch)
case '~':
return SIN, string(ch)
case '∋':
return SIN, string(ch)
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 '/':
chn := s.next()
switch {
case chn == '*':
return s.scanCommentMultiple(ch)
case chn == ' ':
s.undo()
return DIV, string(ch)
default:
s.unread()
s.undo()
return s.scanRegexp(ch)
}
s.unread()
if chn := s.read(); chn == '=' {
return DEC, "-="
case '=':
chn := s.next()
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 '!':
if chn := s.read(); chn == '=' {
return NEQ, "!="
}
s.unread()
case '<':
if chn := s.read(); chn == '-' {
if chn := s.read(); chn == '>' {
return BEDGE, "<->"
chn := s.next()
switch {
case chn == '=':
if s.next() == '=' {
return NEE, "!=="
} else {
s.undo()
return NEQ, "!="
}
s.unread()
return IEDGE, "<-"
case chn == '~':
return SNI, "!~"
default:
s.undo()
return EXC, string(ch)
}
s.unread()
if chn := s.read(); chn == '=' {
return LTE, "<="
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)
}
s.unread()
return LT, string(ch)
case '>':
if chn := s.read(); chn == '=' {
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, "<-"
}
default:
s.undo()
return LT, string(ch)
}
s.unread()
return GT, string(ch)
}
return ILLEGAL, string(ch)
}
// scanWhitespace consumes the current rune and all contiguous whitespace.
func (s *Scanner) scanWhitespace() (tok Token, lit string) {
// scanBlank consumes the current rune and all contiguous whitespace.
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
buf.WriteRune(s.read())
// Read every subsequent whitespace character into the buffer.
// Non-whitespace characters and EOF will cause the loop to exit.
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for {
if ch := s.read(); ch == eof {
if ch := s.next(); ch == eof {
break
} else if !isWhitespace(ch) {
s.unread()
s.undo()
break
} else {
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.
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
buf.WriteRune(s.read())
// Read every subsequent ident character into the buffer.
// Non-ident characters and EOF will cause the loop to exit.
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for {
if ch := s.read(); ch == eof {
if ch := s.next(); ch == eof {
break
} else if !isIdentChar(ch) {
s.unread()
s.undo()
break
} else {
buf.WriteRune(ch)
@ -207,22 +369,25 @@ func (s *Scanner) scanIdent() (tok Token, lit string) {
}
// 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
// Create a buffer and read the current character into it.
// Create a buffer
var buf bytes.Buffer
buf.WriteRune(s.read())
// Read every subsequent ident character into the buffer.
// Non-ident characters and EOF will cause the loop to exit.
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for {
if ch := s.read(); ch == eof {
if ch := s.next(); ch == eof {
break
} else if isNumber(ch) {
buf.WriteRune(ch)
@ -238,7 +403,7 @@ func (s *Scanner) scanNumber() (tok Token, lit string) {
}
buf.WriteRune(ch)
} else {
s.unread()
s.undo()
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) {
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
var buf bytes.Buffer
beg := s.read()
beg := chp[0]
end := beg
if beg == '"' {
@ -280,8 +441,16 @@ func (s *Scanner) scanString() (tok Token, lit string) {
end = '⟩'
}
tok = STRING
// Create a buffer
var buf bytes.Buffer
// Ignore passed in runes
// Read subsequent characters
for {
if ch := s.read(); ch == end {
if ch := s.next(); ch == end {
break
} else if ch == eof {
return ILLEGAL, buf.String()
@ -292,13 +461,13 @@ func (s *Scanner) scanString() (tok Token, lit string) {
tok = REGION
buf.WriteRune(ch)
} else if ch == '\\' {
chn := s.read()
switch chn {
switch chn := s.next(); chn {
default:
buf.WriteRune(chn)
case 'b':
continue
case 't':
tok = REGION
buf.WriteRune('\t')
case 'r':
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
// Create a 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
sub := 0
@ -346,8 +543,20 @@ func (s *Scanner) scanObject() (tok Token, lit string) {
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 {
if ch := s.read(); ch == end && sub == 0 {
if ch := s.next(); ch == end && sub == 0 {
buf.WriteRune(ch)
break
} else if ch == beg {
sub++
@ -358,8 +567,7 @@ func (s *Scanner) scanObject() (tok Token, lit string) {
} else if ch == eof {
return ILLEGAL, buf.String()
} else if ch == '\\' {
chn := s.read()
switch chn {
switch chn := s.next(); chn {
default:
return ILLEGAL, buf.String()
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 len(str) == 0 || str[0] == '"' {
return JSON, string(beg) + str + string(end)
}
return JSON, buf.String()
}
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).
func (s *Scanner) read() rune {
ch, _, err := s.r.ReadRune()
func (s *Scanner) next() rune {
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 {
return eof
}
return ch
s.p = append(s.p, r)
return r
}
// unread places the previously read rune back on the reader.
func (s *Scanner) unread() {
// 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
}
_ = s.r.UnreadRune()
}
// 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'
}
// 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.
func isNumber(ch rune) bool {
return (ch >= '0' && ch <= '9')
}
// isSeparator returns true if the rune is a separator expression.
func isSeparator(ch rune) bool {
return (ch == '.')
// isLetter returns true if the rune is a letter.
func isLetter(ch rune) bool {
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 {
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.

View file

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

View file

@ -29,22 +29,24 @@ const (
literalsBeg
DATE // 1970-01-01
TIME // 1970-01-01T00:00:00+00:00
PATH // :friend
JSON // {"test":true}
IDENT // something
STRING // "something"
REGION // "a multiline \n string"
NUMBER // 123456
DOUBLE // 123.456
REGEX // /.*/
ARRAY // [0,1,2]
DURATION // 13h
DATE // 1970-01-01
TIME // 1970-01-01T00:00:00+00:00
PATH // person->like->person
JSON // {"test":true}
IDENT // something
STRING // "something"
REGION // "a multiline \n string"
NUMBER // 123456
DOUBLE // 123.456
REGEX // /.*/
ARRAY // [0,1,2]
DURATION // 13h
BOUNDPARAM // $1
EAT // @
DOT // .
COMMA // ,
QMARK // ?
LPAREN // (
RPAREN // )
LBRACK // [
@ -66,15 +68,19 @@ const (
DEC // -=
EQ // =
EEQ // ==
EXC // !
NEQ // !=
NEE // !==
ANY // ?=
LT // <
LTE // <=
GT // >
GTE // >=
EQR // =~
NER // !~
SEQ // ∋
SNE // ∌
SIN // ∋
SNI // ∌
INS // ∈
NIS // ∉
OEDGE // ->
IEDGE // <-
@ -89,17 +95,27 @@ const (
ACCEPT
AFTER
ALL
ALLCONTAINEDIN
AND
AS
ASC
AT
BEFORE
BEGIN
BOTH
BY
CANCEL
CODE
COLLECT
COLUMNS
COMMIT
CONTAINS
CONTAINSALL
CONTAINSNONE
CONTAINSSOME
CONTENT
CREATE
CUSTOM
DATABASE
DEFAULT
DEFINE
@ -113,6 +129,7 @@ const (
EXPUNGE
FALSE
FIELD
FOR
FROM
FULL
GROUP
@ -122,14 +139,21 @@ const (
INDEX
INSERT
INTO
IS
LET
LIMIT
MANDATORY
MATCH
MAX
MERGE
MIN
MISSING
MODIFY
MULTI
NAMESPACE
NONE
NONECONTAINEDIN
NOT
NOTNULL
NOW
NULL
@ -144,17 +168,22 @@ const (
REMOVE
RESYNC
RETURN
ROLLBACK
RULES
SELECT
SET
SOMECONTAINEDIN
START
TABLE
TO
TRANSACTION
TRUE
TYPE
UNIQUE
UPDATE
UPSERT
USE
VALIDATE
VERSION
VOID
WHERE
@ -170,22 +199,24 @@ var tokens = [...]string{
// literals
DATE: "DATE",
TIME: "TIME",
PATH: "PATH",
JSON: "JSON",
IDENT: "IDENT",
STRING: "STRING",
REGION: "REGION",
NUMBER: "NUMBER",
DOUBLE: "DOUBLE",
REGEX: "REGEX",
ARRAY: "ARRAY",
DURATION: "DURATION",
DATE: "DATE",
TIME: "TIME",
PATH: "PATH",
JSON: "JSON",
IDENT: "IDENT",
STRING: "STRING",
REGION: "REGION",
NUMBER: "NUMBER",
DOUBLE: "DOUBLE",
REGEX: "REGEX",
ARRAY: "ARRAY",
DURATION: "DURATION",
BOUNDPARAM: "BOUNDPARAM",
EAT: "@",
DOT: ".",
COMMA: ",",
QMARK: "?",
LPAREN: "(",
RPAREN: ")",
LBRACK: "[",
@ -203,89 +234,120 @@ var tokens = [...]string{
DEC: "-=",
EQ: "=",
EEQ: "==",
EXC: "!",
NEQ: "!=",
NEE: "!==",
ANY: "?=",
LT: "<",
LTE: "<=",
GT: ">",
GTE: ">=",
EQR: "=~",
NER: "!~",
SEQ: "∋",
SNE: "∌",
SIN: "∋",
SNI: "∌",
INS: "∈",
NIS: "∉",
OEDGE: "->",
IEDGE: "<-",
BEDGE: "<->",
// keywords
ACCEPT: "ACCEPT",
AFTER: "AFTER",
ALL: "ALL",
AND: "AND",
AS: "AS",
ASC: "ASC",
AT: "AT",
BEFORE: "BEFORE",
BOTH: "BOTH",
BY: "BY",
CODE: "CODE",
COLUMNS: "COLUMNS",
CONTENT: "CONTENT",
CREATE: "CREATE",
DATABASE: "DATABASE",
DEFAULT: "DEFAULT",
DEFINE: "DEFINE",
DELETE: "DELETE",
DESC: "DESC",
DIFF: "DIFF",
DISTINCT: "DISTINCT",
EMPTY: "EMPTY",
ENUM: "ENUM",
EXPLAIN: "EXPLAIN",
EXPUNGE: "EXPUNGE",
FALSE: "FALSE",
FIELD: "FIELD",
FROM: "FROM",
FULL: "FULL",
GROUP: "GROUP",
HISTORY: "HISTORY",
ID: "ID",
IN: "IN",
INDEX: "INDEX",
INSERT: "INSERT",
INTO: "INTO",
LIMIT: "LIMIT",
MANDATORY: "MANDATORY",
MAX: "MAX",
MERGE: "MERGE",
MIN: "MIN",
MODIFY: "MODIFY",
NAMESPACE: "NAMESPACE",
NONE: "NONE",
NOTNULL: "NOTNULL",
NOW: "NOW",
NULL: "NULL",
ON: "ON",
OR: "OR",
ORDER: "ORDER",
READONLY: "READONLY",
RECORD: "RECORD",
REJECT: "REJECT",
RELATE: "RELATE",
REMOVE: "REMOVE",
RESYNC: "RESYNC",
RETURN: "RETURN",
SELECT: "SELECT",
SET: "SET",
START: "START",
TABLE: "TABLE",
TO: "TO",
TRUE: "TRUE",
TYPE: "TYPE",
UNIQUE: "UNIQUE",
UPDATE: "UPDATE",
UPSERT: "UPSERT",
USE: "USE",
VERSION: "VERSION",
VOID: "VOID",
WHERE: "WHERE",
ACCEPT: "ACCEPT",
AFTER: "AFTER",
ALL: "ALL",
ALLCONTAINEDIN: "ALLCONTAINEDIN",
AND: "AND",
AS: "AS",
ASC: "ASC",
AT: "AT",
BEFORE: "BEFORE",
BEGIN: "BEGIN",
BOTH: "BOTH",
BY: "BY",
CANCEL: "CANCEL",
CODE: "CODE",
COLLECT: "COLLECT",
COLUMNS: "COLUMNS",
COMMIT: "COMMIT",
CONTAINS: "CONTAINS",
CONTAINSALL: "CONTAINSALL",
CONTAINSNONE: "CONTAINSNONE",
CONTAINSSOME: "CONTAINSSOME",
CONTENT: "CONTENT",
CREATE: "CREATE",
CUSTOM: "CUSTOM",
DATABASE: "DATABASE",
DEFAULT: "DEFAULT",
DEFINE: "DEFINE",
DELETE: "DELETE",
DESC: "DESC",
DIFF: "DIFF",
DISTINCT: "DISTINCT",
EMPTY: "EMPTY",
ENUM: "ENUM",
EXPLAIN: "EXPLAIN",
EXPUNGE: "EXPUNGE",
FALSE: "FALSE",
FIELD: "FIELD",
FOR: "FOR",
FROM: "FROM",
FULL: "FULL",
GROUP: "GROUP",
HISTORY: "HISTORY",
ID: "ID",
IN: "IN",
INDEX: "INDEX",
INSERT: "INSERT",
INTO: "INTO",
IS: "IS",
LET: "LET",
LIMIT: "LIMIT",
MANDATORY: "MANDATORY",
MATCH: "MATCH",
MAX: "MAX",
MERGE: "MERGE",
MIN: "MIN",
MISSING: "MISSING",
MODIFY: "MODIFY",
MULTI: "MULTI",
NAMESPACE: "NAMESPACE",
NONE: "NONE",
NONECONTAINEDIN: "NONECONTAINEDIN",
NOT: "NOT",
NOTNULL: "NOTNULL",
NOW: "NOW",
NULL: "NULL",
ON: "ON",
OR: "OR",
ORDER: "ORDER",
READONLY: "READONLY",
RECORD: "RECORD",
REJECT: "REJECT",
RELATE: "RELATE",
REMOVE: "REMOVE",
RESYNC: "RESYNC",
RETURN: "RETURN",
ROLLBACK: "ROLLBACK",
RULES: "RULES",
SELECT: "SELECT",
SET: "SET",
SOMECONTAINEDIN: "SOMECONTAINEDIN",
START: "START",
TABLE: "TABLE",
TO: "TO",
TRANSACTION: "TRANSACTION",
TRUE: "TRUE",
TYPE: "TYPE",
UNIQUE: "UNIQUE",
UPDATE: "UPDATE",
UPSERT: "UPSERT",
USE: "USE",
VALIDATE: "VALIDATE",
VERSION: "VERSION",
VOID: "VOID",
WHERE: "WHERE",
}
var literals map[string]Token
@ -325,7 +387,11 @@ func (tok Token) precedence() int {
return 1
case AND:
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
case ADD, SUB:
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
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"}
tok, lit, err := p.shouldBe(IDENT)
_, lit, err := p.shouldBe(IDENT, CUSTOM)
if err != nil {
return Ident(""), err
return string(""), &ParseError{Found: lit, Expected: 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:
return &Void{}, nil
case MISSING:
return &Void{}, nil
case EMPTY:
return &Empty{}, nil
@ -93,10 +96,10 @@ func declare(tok Token, lit string) (interface{}, error) {
return &Desc{}, nil
case ID:
return Ident(lit), nil
return &Ident{lit}, nil
case IDENT:
return Ident(lit), nil
return &Ident{lit}, nil
case NOW:
return time.Now(), nil
@ -111,7 +114,7 @@ func declare(tok Token, lit string) (interface{}, error) {
return regexp.Compile(lit)
case NUMBER:
return strconv.ParseInt(lit, 10, 64)
return strconv.ParseFloat(lit, 64)
case DOUBLE:
return strconv.ParseFloat(lit, 64)
@ -120,7 +123,7 @@ func declare(tok Token, lit string) (interface{}, error) {
return time.ParseDuration(lit)
case ARRAY:
var j interface{}
var j []interface{}
json.Unmarshal([]byte(lit), &j)
if j == nil {
return j, fmt.Errorf("Invalid JSON: %s", lit)
@ -128,7 +131,7 @@ func declare(tok Token, lit string) (interface{}, error) {
return j, nil
case JSON:
var j interface{}
var j map[string]interface{}
json.Unmarshal([]byte(lit), &j)
if j == nil {
return j, fmt.Errorf("Invalid JSON: %s", lit)