diff --git a/sql/ast.go b/sql/ast.go index c641abb5..5b53e0c5 100644 --- a/sql/ast.go +++ b/sql/ast.go @@ -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 diff --git a/sql/cond.go b/sql/cond.go index 8f0fbaa1..bb235f9d 100644 --- a/sql/cond.go +++ b/sql/cond.go @@ -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 } diff --git a/sql/data.go b/sql/data.go index f7c5ce1e..8c6aed77 100644 --- a/sql/data.go +++ b/sql/data.go @@ -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"}} } diff --git a/sql/exprs.go b/sql/exprs.go index 499f952e..dc23b0dc 100644 --- a/sql/exprs.go +++ b/sql/exprs.go @@ -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) diff --git a/sql/field.go b/sql/field.go index c69e1d91..c261f18e 100644 --- a/sql/field.go +++ b/sql/field.go @@ -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 } diff --git a/sql/index.go b/sql/index.go index fd737e3d..832b5a08 100644 --- a/sql/index.go +++ b/sql/index.go @@ -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 } diff --git a/sql/modify.go b/sql/modify.go index e21b707e..406e4a80 100644 --- a/sql/modify.go +++ b/sql/modify.go @@ -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 } diff --git a/sql/parser.go b/sql/parser.go index 7c6aa6a3..7719e56a 100644 --- a/sql/parser.go +++ b/sql/parser.go @@ -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: diff --git a/sql/rules.go b/sql/rules.go index 4fde0dcf..7174fe37 100644 --- a/sql/rules.go +++ b/sql/rules.go @@ -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 } diff --git a/sql/scanner.go b/sql/scanner.go index 3232512d..be5ad777 100644 --- a/sql/scanner.go +++ b/sql/scanner.go @@ -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. diff --git a/sql/select.go b/sql/select.go index eac203f4..c1ea8007 100644 --- a/sql/select.go +++ b/sql/select.go @@ -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) diff --git a/sql/sql_test.go b/sql/sql_test.go index a7e41582..60e1e630 100644 --- a/sql/sql_test.go +++ b/sql/sql_test.go @@ -140,11 +140,11 @@ func Test_Parse_Queries_Malformed(t *testing.T) { }, { sql: `!`, - err: "Found `!` but expected `USE, SELECT, CREATE, UPDATE, INSERT, UPSERT, MODIFY, DELETE, RELATE, RECORD, DEFINE, RESYNC, REMOVE`", + err: "Found `!` but expected `USE, LET, BEGIN, CANCEL, COMMIT, ROLLBACK, SELECT, CREATE, UPDATE, INSERT, UPSERT, MODIFY, DELETE, RELATE, RECORD, DEFINE, RESYNC, REMOVE`", }, { sql: `SELECT * FROM person;;;`, - err: "Found `;` but expected `USE, SELECT, CREATE, UPDATE, INSERT, UPSERT, MODIFY, DELETE, RELATE, RECORD, DEFINE, RESYNC, REMOVE`", + err: "Found `;` but expected `USE, LET, BEGIN, CANCEL, COMMIT, ROLLBACK, SELECT, CREATE, UPDATE, INSERT, UPSERT, MODIFY, DELETE, RELATE, RECORD, DEFINE, RESYNC, REMOVE`", }, } @@ -256,8 +256,8 @@ func Test_Parse_Queries_Explain(t *testing.T) { sql: `EXPLAIN SELECT ALL FROM person`, res: &Query{Statements: []Statement{&SelectStatement{ EX: true, - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, + Expr: []*Field{{Expr: &All{}, Alias: "ALL"}}, + What: []Expr{&Table{"person"}}, }}}, }, } @@ -294,15 +294,15 @@ func Test_Parse_Queries_Select(t *testing.T) { { sql: `SELECT * FROM person;`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, }}}, }, { sql: `SELECT ALL FROM person;`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, + Expr: []*Field{{Expr: &All{}, Alias: "ALL"}}, + What: []Expr{&Table{"person"}}, }}}, }, { @@ -324,198 +324,141 @@ func Test_Parse_Queries_Select(t *testing.T) { { sql: `SELECT * FROM person`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, }}}, }, { sql: `SELECT * FROM person, tweet`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person"), Table("tweet")}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}, &Table{"tweet"}}, }}}, }, { sql: `SELECT * FROM @person:1a`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "person", ID: "1a"}}, }}}, }, { sql: `SELECT * FROM @person:123456`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{&Thing{TB: "person", ID: int64(123456)}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Thing{TB: "person", ID: float64(123456)}}, }}}, }, { sql: `SELECT * FROM @person:123.456`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "person", ID: float64(123.456)}}, }}}, }, { sql: `SELECT * FROM @person:123.456.789.012`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "person", ID: "123.456.789.012"}}, }}}, }, { sql: `SELECT * FROM @person:⟨123.456.789.012⟩`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{&Thing{TB: "person", ID: "123.456.789.012"}}, - }}}, - }, - { - sql: `SELECT * FROM @person:{123.456.789.012}`, - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "person", ID: "123.456.789.012"}}, }}}, }, { sql: `SELECT * FROM @person:⟨A250C5A3-948F-4657-88AD-FF5F27B5B24E⟩`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{&Thing{TB: "person", ID: "A250C5A3-948F-4657-88AD-FF5F27B5B24E"}}, - }}}, - }, - { - sql: `SELECT * FROM @person:{A250C5A3-948F-4657-88AD-FF5F27B5B24E}`, - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "person", ID: "A250C5A3-948F-4657-88AD-FF5F27B5B24E"}}, }}}, }, { sql: `SELECT * FROM @person:⟨8250C5A3-948F-4657-88AD-FF5F27B5B24E⟩`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{&Thing{TB: "person", ID: "8250C5A3-948F-4657-88AD-FF5F27B5B24E"}}, - }}}, - }, - { - sql: `SELECT * FROM @person:{8250C5A3-948F-4657-88AD-FF5F27B5B24E}`, - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "person", ID: "8250C5A3-948F-4657-88AD-FF5F27B5B24E"}}, }}}, }, { sql: `SELECT * FROM @person:⟨Tobie Morgan Hitchcock⟩`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "person", ID: "Tobie Morgan Hitchcock"}}, }}}, }, { - sql: `SELECT * FROM @person:{Tobie Morgan Hitchcock}`, + sql: `SELECT * FROM @⟨email addresses⟩:⟨tobie@abcum.com⟩`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{&Thing{TB: "person", ID: "Tobie Morgan Hitchcock"}}, - }}}, - }, - { - sql: `SELECT * FROM @{email addresses}:⟨tobie@abcum.com⟩`, - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "email addresses", ID: "tobie@abcum.com"}}, }}}, }, { - sql: `SELECT * FROM @{email addresses}:{tobie@abcum.com}`, + sql: `SELECT * FROM @⟨email addresses⟩:⟨tobie+spam@abcum.com⟩`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{&Thing{TB: "email addresses", ID: "tobie@abcum.com"}}, - }}}, - }, - { - sql: `SELECT * FROM @{email addresses}:⟨tobie+spam@abcum.com⟩`, - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, What: []Expr{&Thing{TB: "email addresses", ID: "tobie+spam@abcum.com"}}, }}}, }, { - sql: `SELECT * FROM @{email addresses}:{tobie+spam@abcum.com}`, - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{&Thing{TB: "email addresses", ID: "tobie+spam@abcum.com"}}, - }}}, - }, - { - sql: `SELECT * FROM @{email addresses}:⟨this\qis\nodd⟩`, + sql: `SELECT * FROM @⟨email addresses⟩:⟨this\qis\nodd⟩`, err: "Found `thisqis\nodd` but expected `table id`", }, - { - sql: `SELECT * FROM @{email addresses}:{this\qis\nodd}`, - err: "Found `this` but expected `table id`", - }, - { - sql: `SELECT * FROM @{email addresses}:⟨this\nis\nodd⟩`, - err: "Found `this\nis\nodd` but expected `table id`", - }, - { - sql: `SELECT * FROM @{email addresses}:{this\nis\nodd}`, - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{&Thing{TB: "email addresses", ID: "this\\nis\\nodd"}}, - }}}, - }, { sql: `SELECT *, temp AS test FROM person`, res: &Query{Statements: []Statement{&SelectStatement{ Expr: []*Field{ - {Expr: &All{}}, - {Expr: Ident("temp"), Alias: Ident("test")}, + {Expr: &All{}, Alias: "*"}, + {Expr: &Ident{"temp"}, Alias: "test"}, }, - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, }}}, }, { sql: "SELECT `email addresses` AS emails FROM person", res: &Query{Statements: []Statement{&SelectStatement{ Expr: []*Field{ - {Expr: Ident("email addresses"), Alias: Ident("emails")}, + {Expr: &Ident{"email addresses"}, Alias: "emails"}, }, - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, }}}, }, { sql: "SELECT emails AS `email addresses` FROM person", res: &Query{Statements: []Statement{&SelectStatement{ Expr: []*Field{ - {Expr: Ident("emails"), Alias: Ident("email addresses")}, + {Expr: &Ident{"emails"}, Alias: "email addresses"}, }, - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, }}}, }, { - sql: "SELECT ALL FROM person WHERE id = '\x0A'", + sql: "SELECT * FROM person WHERE id = '\x0A'", res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("id"), Op: "=", RHS: "\n"}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{&BinaryExpression{LHS: &Ident{"id"}, Op: EQ, RHS: "\n"}}, }}}, }, { - sql: "SELECT ALL FROM person WHERE id = '\x0D'", + sql: "SELECT * FROM person WHERE id = '\x0D'", res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("id"), Op: "=", RHS: "\r"}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{&BinaryExpression{LHS: &Ident{"id"}, Op: EQ, RHS: "\r"}}, }}}, }, { - sql: `SELECT ALL FROM person WHERE id = "\b\n\r\t"`, + sql: `SELECT * FROM person WHERE id = "\b\n\r\t"`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("id"), Op: "=", RHS: "\n\r\t"}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{&BinaryExpression{LHS: &Ident{"id"}, Op: EQ, RHS: "\n\r\t"}}, }}}, }, { @@ -524,54 +467,131 @@ func Test_Parse_Queries_Select(t *testing.T) { }, { sql: `SELECT * FROM person WHERE id`, - err: "Found `` but expected `IN, =, !=, >, <, >=, <=, =~, !~, ∋, ∌`", + err: "Found `` but expected `IS, IN, =, !=, ==, !==, ?=, <, <=, >, >=, ∋, ∌, ∈, ∉, CONTAINS, CONTAINSALL, CONTAINSNONE, CONTAINSSOME, ALLCONTAINEDIN, NONECONTAINEDIN, SOMECONTAINEDIN`", }, { - sql: `SELECT * FROM person WHERE id =`, + sql: `SELECT * FROM person WHERE id = `, err: "Found `` but expected `field value`", }, { sql: `SELECT * FROM person WHERE id = 1`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("id"), Op: "=", RHS: int64(1)}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{&BinaryExpression{LHS: &Ident{"id"}, Op: EQ, RHS: float64(1)}}, }}}, }, { - sql: `SELECT * FROM person WHERE old != EMPTY AND old = true`, + sql: `SELECT * FROM person WHERE old = EMPTY`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, Cond: []Expr{ - &BinaryExpression{LHS: Ident("old"), Op: "!=", RHS: &Empty{}}, - &BinaryExpression{LHS: Ident("old"), Op: "=", RHS: true}, + &BinaryExpression{LHS: &Ident{"old"}, Op: EQ, RHS: &Empty{}}, }, }}}, }, { - sql: `SELECT * FROM person WHERE old != EMPTY AND old = false`, + sql: `SELECT * FROM person WHERE old != EMPTY`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, Cond: []Expr{ - &BinaryExpression{LHS: Ident("old"), Op: "!=", RHS: &Empty{}}, - &BinaryExpression{LHS: Ident("old"), Op: "=", RHS: false}, + &BinaryExpression{LHS: &Ident{"old"}, Op: NEQ, RHS: &Empty{}}, }, }}}, }, { - sql: `SELECT * FROM person WHERE id != null AND id != EMPTY AND id > 13.9 AND id < 31 AND id >= 15 AND id <= 29.9`, + sql: `SELECT * FROM person WHERE old = MISSING`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, Cond: []Expr{ - &BinaryExpression{LHS: Ident("id"), Op: "!=", RHS: &Null{}}, - &BinaryExpression{LHS: Ident("id"), Op: "!=", RHS: &Empty{}}, - &BinaryExpression{LHS: Ident("id"), Op: ">", RHS: float64(13.9)}, - &BinaryExpression{LHS: Ident("id"), Op: "<", RHS: int64(31)}, - &BinaryExpression{LHS: Ident("id"), Op: ">=", RHS: int64(15)}, - &BinaryExpression{LHS: Ident("id"), Op: "<=", RHS: float64(29.9)}, + &BinaryExpression{LHS: &Ident{"old"}, Op: EQ, RHS: &Void{}}, + }, + }}}, + }, + { + sql: `SELECT * FROM person WHERE old != MISSING`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{ + &BinaryExpression{LHS: &Ident{"old"}, Op: NEQ, RHS: &Void{}}, + }, + }}}, + }, + { + sql: `SELECT * FROM person WHERE old IS EMPTY`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{ + &BinaryExpression{LHS: &Ident{"old"}, Op: EQ, RHS: &Empty{}}, + }, + }}}, + }, + { + sql: `SELECT * FROM person WHERE old IS NOT EMPTY`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{ + &BinaryExpression{LHS: &Ident{"old"}, Op: NEQ, RHS: &Empty{}}, + }, + }}}, + }, + { + sql: `SELECT * FROM person WHERE old IS MISSING`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{ + &BinaryExpression{LHS: &Ident{"old"}, Op: EQ, RHS: &Void{}}, + }, + }}}, + }, + { + sql: `SELECT * FROM person WHERE old IS NOT MISSING`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{ + &BinaryExpression{LHS: &Ident{"old"}, Op: NEQ, RHS: &Void{}}, + }, + }}}, + }, + { + sql: `SELECT * FROM person WHERE old = true`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{ + &BinaryExpression{LHS: &Ident{"old"}, Op: EQ, RHS: true}, + }, + }}}, + }, + { + sql: `SELECT * FROM person WHERE old = false`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{ + &BinaryExpression{LHS: &Ident{"old"}, Op: EQ, RHS: false}, + }, + }}}, + }, + { + sql: `SELECT * FROM person WHERE id != null AND id > 13.9 AND id < 31 AND id >= 15 AND id <= 29.9`, + res: &Query{Statements: []Statement{&SelectStatement{ + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{ + &BinaryExpression{LHS: &Ident{"id"}, Op: NEQ, RHS: &Null{}}, + &BinaryExpression{LHS: &Ident{"id"}, Op: GT, RHS: float64(13.9)}, + &BinaryExpression{LHS: &Ident{"id"}, Op: LT, RHS: float64(31)}, + &BinaryExpression{LHS: &Ident{"id"}, Op: GTE, RHS: float64(15)}, + &BinaryExpression{LHS: &Ident{"id"}, Op: LTE, RHS: float64(29.9)}, }, }}}, }, @@ -582,14 +602,14 @@ func Test_Parse_Queries_Select(t *testing.T) { { sql: `SELECT * FROM person WHERE test IN ["London","Paris"]`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("test"), Op: "IN", RHS: []interface{}{"London", "Paris"}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{&BinaryExpression{LHS: &Ident{"test"}, Op: INS, RHS: []interface{}{"London", "Paris"}}}, }}}, }, { sql: `SELECT * FROM person WHERE test = {`, - err: "Found `` but expected `field value`", + err: "Found `{` but expected `field value`", }, { sql: `SELECT * FROM person WHERE test = {"name","London"}`, @@ -597,42 +617,34 @@ func Test_Parse_Queries_Select(t *testing.T) { }, { sql: "SELECT * FROM person WHERE test = {\"name\":\"\x0A\"}", - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("test"), Op: "=", RHS: map[string]interface{}{"name": ""}}}, - }}}, + err: "Invalid JSON: {\"name\":\"\n\"}", }, { sql: "SELECT * FROM person WHERE test = {\"name\":\"\x0D\"}", - res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("test"), Op: "=", RHS: map[string]interface{}{"name": ""}}}, - }}}, + err: "Invalid JSON: {\"name\":\"\r\"}", }, { sql: `SELECT * FROM person WHERE test = {"name":"London"}`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("test"), Op: "=", RHS: map[string]interface{}{"name": "London"}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{&BinaryExpression{LHS: &Ident{"test"}, Op: EQ, RHS: map[string]interface{}{"name": "London"}}}, }}}, }, { sql: `SELECT * FROM person WHERE test = {"name":"\b\t\r\n\f\"\\"}`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("test"), Op: "=", RHS: map[string]interface{}{"name": "\b\t\r\n\f\"\\"}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{&BinaryExpression{LHS: &Ident{"test"}, Op: EQ, RHS: map[string]interface{}{"name": "\b\t\r\n\f\"\\"}}}, }}}, }, { sql: `SELECT * FROM person WHERE test = {"name":{"f":"first", "l":"last"}}`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, - Cond: []Expr{&BinaryExpression{LHS: Ident("test"), Op: "=", RHS: map[string]interface{}{"name": map[string]interface{}{"f": "first", "l": "last"}}}}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, + Cond: []Expr{&BinaryExpression{LHS: &Ident{"test"}, Op: EQ, RHS: map[string]interface{}{"name": map[string]interface{}{"f": "first", "l": "last"}}}}, }}}, }, } @@ -645,13 +657,13 @@ func Test_Parse_Queries_Select(t *testing.T) { tests = append(tests, tester{ sql: `SELECT * FROM person WHERE bday >= "1987-06-22" AND bday >= "1987-06-22T08:00:00Z" AND bday >= "1987-06-22T08:30:00.193943735Z" AND bday <= "2016-03-14T11:19:31.193943735Z"`, res: &Query{Statements: []Statement{&SelectStatement{ - Expr: []*Field{{Expr: &All{}}}, - What: []Expr{Table("person")}, + Expr: []*Field{{Expr: &All{}, Alias: "*"}}, + What: []Expr{&Table{"person"}}, Cond: []Expr{ - &BinaryExpression{LHS: Ident("bday"), Op: ">=", RHS: bday1}, - &BinaryExpression{LHS: Ident("bday"), Op: ">=", RHS: bday2}, - &BinaryExpression{LHS: Ident("bday"), Op: ">=", RHS: bday3}, - &BinaryExpression{LHS: Ident("bday"), Op: "<=", RHS: bday4}, + &BinaryExpression{LHS: &Ident{"bday"}, Op: GTE, RHS: bday1}, + &BinaryExpression{LHS: &Ident{"bday"}, Op: GTE, RHS: bday2}, + &BinaryExpression{LHS: &Ident{"bday"}, Op: GTE, RHS: bday3}, + &BinaryExpression{LHS: &Ident{"bday"}, Op: LTE, RHS: bday4}, }, }}}, }) @@ -677,7 +689,7 @@ func Test_Parse_Queries_Create(t *testing.T) { sql: `CREATE person`, res: &Query{Statements: []Statement{&CreateStatement{ What: []Expr{ - Table("person"), + &Table{"person"}, }, }}}, }, @@ -696,8 +708,8 @@ func Test_Parse_Queries_Create(t *testing.T) { { sql: `CREATE person SET firstname = VOID`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, - Data: []Expr{&BinaryExpression{LHS: Ident("firstname"), Op: "=", RHS: &Void{}}}, + What: []Expr{&Table{"person"}}, + Data: []Expr{&BinaryExpression{LHS: &Ident{"firstname"}, Op: EQ, RHS: &Void{}}}, }}}, }, { @@ -707,8 +719,8 @@ func Test_Parse_Queries_Create(t *testing.T) { { sql: `CREATE person SET firstname = "Tobie"`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, - Data: []Expr{&BinaryExpression{LHS: Ident("firstname"), Op: "=", RHS: "Tobie"}}, + What: []Expr{&Table{"person"}}, + Data: []Expr{&BinaryExpression{LHS: &Ident{"firstname"}, Op: EQ, RHS: "Tobie"}}, }}}, }, { @@ -726,7 +738,7 @@ func Test_Parse_Queries_Create(t *testing.T) { { sql: `CREATE person MERGE {"firstname":"Tobie"}`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Data: []Expr{&MergeExpression{JSON: map[string]interface{}{"firstname": "Tobie"}}}, }}}, }, @@ -745,56 +757,56 @@ func Test_Parse_Queries_Create(t *testing.T) { { sql: `CREATE person CONTENT {"firstname":"Tobie"}`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Data: []Expr{&ContentExpression{JSON: map[string]interface{}{"firstname": "Tobie"}}}, }}}, }, { sql: `CREATE person RETURN ID`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: ID, }}}, }, { sql: `CREATE person RETURN NONE`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: NONE, }}}, }, { sql: `CREATE person RETURN FULL`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: FULL, }}}, }, { sql: `CREATE person RETURN BOTH`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: BOTH, }}}, }, { sql: `CREATE person RETURN DIFF`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: DIFF, }}}, }, { sql: `CREATE person RETURN BEFORE`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: BEFORE, }}}, }, { sql: `CREATE person RETURN AFTER`, res: &Query{Statements: []Statement{&CreateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: AFTER, }}}, }, @@ -825,7 +837,7 @@ func Test_Parse_Queries_Update(t *testing.T) { sql: `UPDATE person`, res: &Query{Statements: []Statement{&UpdateStatement{ What: []Expr{ - Table("person"), + &Table{"person"}, }, }}}, }, @@ -844,8 +856,8 @@ func Test_Parse_Queries_Update(t *testing.T) { { sql: `UPDATE person SET firstname = VOID`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, - Data: []Expr{&BinaryExpression{LHS: Ident("firstname"), Op: "=", RHS: &Void{}}}, + What: []Expr{&Table{"person"}}, + Data: []Expr{&BinaryExpression{LHS: &Ident{"firstname"}, Op: EQ, RHS: &Void{}}}, }}}, }, { @@ -855,8 +867,8 @@ func Test_Parse_Queries_Update(t *testing.T) { { sql: `UPDATE person SET firstname = "Tobie"`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, - Data: []Expr{&BinaryExpression{LHS: Ident("firstname"), Op: "=", RHS: "Tobie"}}, + What: []Expr{&Table{"person"}}, + Data: []Expr{&BinaryExpression{LHS: &Ident{"firstname"}, Op: EQ, RHS: "Tobie"}}, }}}, }, { @@ -874,7 +886,7 @@ func Test_Parse_Queries_Update(t *testing.T) { { sql: `UPDATE person MERGE {"firstname":"Tobie"}`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Data: []Expr{&MergeExpression{JSON: map[string]interface{}{"firstname": "Tobie"}}}, }}}, }, @@ -893,56 +905,56 @@ func Test_Parse_Queries_Update(t *testing.T) { { sql: `UPDATE person CONTENT {"firstname":"Tobie"}`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Data: []Expr{&ContentExpression{JSON: map[string]interface{}{"firstname": "Tobie"}}}, }}}, }, { sql: `UPDATE person RETURN ID`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: ID, }}}, }, { sql: `UPDATE person RETURN NONE`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: NONE, }}}, }, { sql: `UPDATE person RETURN FULL`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: FULL, }}}, }, { sql: `UPDATE person RETURN BOTH`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: BOTH, }}}, }, { sql: `UPDATE person RETURN DIFF`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: DIFF, }}}, }, { sql: `UPDATE person RETURN BEFORE`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: BEFORE, }}}, }, { sql: `UPDATE person RETURN AFTER`, res: &Query{Statements: []Statement{&UpdateStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: AFTER, }}}, }, @@ -973,6 +985,10 @@ func Test_Parse_Queries_Modify(t *testing.T) { sql: `MODIFY @person:test DIFF`, err: "Found `` but expected `json`", }, + { + sql: `MODIFY @person:test DIFF {invalid}`, + err: "Found `{invalid}` but expected `json`", + }, { sql: `MODIFY @person:test DIFF {"diff": true}`, res: &Query{Statements: []Statement{&ModifyStatement{ @@ -1067,56 +1083,56 @@ func Test_Parse_Queries_Delete(t *testing.T) { sql: `DELETE person`, res: &Query{Statements: []Statement{&DeleteStatement{ What: []Expr{ - Table("person"), + &Table{"person"}, }, }}}, }, { sql: `DELETE person RETURN ID`, res: &Query{Statements: []Statement{&DeleteStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: ID, }}}, }, { sql: `DELETE person RETURN NONE`, res: &Query{Statements: []Statement{&DeleteStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: NONE, }}}, }, { sql: `DELETE person RETURN FULL`, res: &Query{Statements: []Statement{&DeleteStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: FULL, }}}, }, { sql: `DELETE person RETURN BOTH`, res: &Query{Statements: []Statement{&DeleteStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: BOTH, }}}, }, { sql: `DELETE person RETURN DIFF`, res: &Query{Statements: []Statement{&DeleteStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: DIFF, }}}, }, { sql: `DELETE person RETURN BEFORE`, res: &Query{Statements: []Statement{&DeleteStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: BEFORE, }}}, }, { sql: `DELETE person RETURN AFTER`, res: &Query{Statements: []Statement{&DeleteStatement{ - What: []Expr{Table("person")}, + What: []Expr{&Table{"person"}}, Echo: AFTER, }}}, }, @@ -1157,16 +1173,17 @@ func Test_Parse_Queries_Define(t *testing.T) { var tests = []tester{ { sql: `DEFINE`, - err: "Found `` but expected `TABLE, FIELD, INDEX`", + err: "Found `` but expected `RULES, TABLE, FIELD, INDEX`", }, + // ---------------------------------------------------------------------- { sql: `DEFINE TABLE`, - err: "Found `` but expected `table name`", + err: "Found `` but expected `name`", }, { sql: `DEFINE TABLE person`, res: &Query{Statements: []Statement{&DefineTableStatement{ - What: []Table{Table("person")}, + What: []string{"person"}, }}}, }, { @@ -1174,6 +1191,68 @@ func Test_Parse_Queries_Define(t *testing.T) { err: "Found `something` but expected `EOF, ;`", }, // ---------------------------------------------------------------------- + { + sql: `DEFINE RULES`, + err: "Found `` but expected `ON`", + }, + { + sql: `DEFINE RULES ON`, + err: "Found `` but expected `name`", + }, + { + sql: `DEFINE RULES ON person`, + err: "Found `` but expected `FOR`", + }, + { + sql: `DEFINE RULES ON person FOR`, + err: "Found `` but expected `SELECT, CREATE, UPDATE, DELETE, RELATE, RECORD`", + }, + { + sql: `DEFINE RULES ON person FOR select, create, update, delete`, + err: "Found `` but expected `ACCEPT, REJECT, CUSTOM`", + }, + { + sql: `DEFINE RULES ON person FOR select, create, update, delete ACCEPT`, + res: &Query{Statements: []Statement{&DefineRulesStatement{ + What: []string{"person"}, + When: []string{"SELECT", "CREATE", "UPDATE", "DELETE"}, + Rule: "ACCEPT", + }}}, + }, + { + sql: `DEFINE RULES ON person FOR select, create, update, delete ACCEPT something`, + err: "Found `something` but expected `EOF, ;`", + }, + { + sql: `DEFINE RULES ON person FOR select, create, update, delete REJECT`, + res: &Query{Statements: []Statement{&DefineRulesStatement{ + What: []string{"person"}, + When: []string{"SELECT", "CREATE", "UPDATE", "DELETE"}, + Rule: "REJECT", + }}}, + }, + { + sql: `DEFINE RULES ON person FOR select, create, update, delete REJECT something`, + err: "Found `something` but expected `EOF, ;`", + }, + { + sql: `DEFINE RULES ON person FOR select, create, update, delete CUSTOM`, + err: "Found `` but expected `js/lua script`", + }, + { + sql: `DEFINE RULES ON person FOR select, create, update, delete CUSTOM "return true"`, + res: &Query{Statements: []Statement{&DefineRulesStatement{ + What: []string{"person"}, + When: []string{"SELECT", "CREATE", "UPDATE", "DELETE"}, + Rule: "CUSTOM", + Code: "return true", + }}}, + }, + { + sql: `DEFINE RULES ON person FOR select, create, update, delete CUSTOM "return true" something`, + err: "Found `something` but expected `EOF, ;`", + }, + // ---------------------------------------------------------------------- { sql: `DEFINE FIELD`, err: "Found `` but expected `name`", @@ -1184,7 +1263,7 @@ func Test_Parse_Queries_Define(t *testing.T) { }, { sql: `DEFINE FIELD temp ON`, - err: "Found `` but expected `table name`", + err: "Found `` but expected `name`", }, { sql: `DEFINE FIELD temp ON person`, @@ -1192,146 +1271,159 @@ func Test_Parse_Queries_Define(t *testing.T) { }, { sql: `DEFINE FIELD temp ON person TYPE`, - err: "Found `` but expected `IDENT`", + err: "Found `` but expected `any, url, uuid, color, email, phone, array, object, domain, string, number, custom, boolean, datetime, latitude, longitude`", + }, + { + sql: `DEFINE FIELD temp ON person TYPE something`, + err: "Found `something` but expected `any, url, uuid, color, email, phone, array, object, domain, string, number, custom, boolean, datetime, latitude, longitude`", }, { sql: `DEFINE FIELD temp ON person TYPE any`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE url`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("url"), + Name: "temp", + What: []string{"person"}, + Type: "url", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE email`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("email"), + Name: "temp", + What: []string{"person"}, + Type: "email", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE phone`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("phone"), + Name: "temp", + What: []string{"person"}, + Type: "phone", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE array`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("array"), + Name: "temp", + What: []string{"person"}, + Type: "array", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE object`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("object"), + Name: "temp", + What: []string{"person"}, + Type: "object", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE string`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("string"), + Name: "temp", + What: []string{"person"}, + Type: "string", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE number`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("number"), + Name: "temp", + What: []string{"person"}, + Type: "number", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE custom`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("custom"), + Name: "temp", + What: []string{"person"}, + Type: "custom", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE custom ENUM ["default","notdefault"]`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("custom"), + Name: "temp", + What: []string{"person"}, + Type: "custom", Enum: []interface{}{"default", "notdefault"}, }}}, }, + { + sql: `DEFINE FIELD temp ON person TYPE custom ENUM ["default" "notdefault"]`, + err: `Invalid JSON: ["default" "notdefault"]`, + }, { sql: `DEFINE FIELD temp ON person TYPE any DEFAULT true`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Default: true, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any DEFAULT false`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Default: false, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any DEFAULT 100`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), - Default: int64(100), + Name: "temp", + What: []string{"person"}, + Type: "any", + Default: float64(100), }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any DEFAULT "default"`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Default: "default", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any DEFAULT "this\nis\nsome\ntext"`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Default: "this\nis\nsome\ntext", }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any DEFAULT {"default":true}`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Default: map[string]interface{}{"default": true}, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any DEFAULT something`, - err: "Found `something` but expected `NULL, NOW, DATE, TIME, TRUE, FALSE, NUMBER, STRING, REGION, ARRAY, JSON`", + res: &Query{Statements: []Statement{&DefineFieldStatement{ + Name: "temp", + What: []string{"person"}, + Type: "any", + Default: &Ident{"something"}, + }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any MIN`, @@ -1340,27 +1432,27 @@ func Test_Parse_Queries_Define(t *testing.T) { { sql: `DEFINE FIELD temp ON person TYPE any MIN 1`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Min: 1, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any MIN 1.0`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Min: 1, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any MIN 1.0001`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Min: 1.0001, }}}, }, @@ -1375,27 +1467,27 @@ func Test_Parse_Queries_Define(t *testing.T) { { sql: `DEFINE FIELD temp ON person TYPE any MAX 100`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Max: 100, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any MAX 100.0`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Max: 100, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any MAX 100.0001`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Max: 100.0001, }}}, }, @@ -1403,87 +1495,148 @@ func Test_Parse_Queries_Define(t *testing.T) { sql: `DEFINE FIELD temp ON person TYPE any MAX something`, err: "Found `something` but expected `number`", }, + { + sql: `DEFINE FIELD temp ON person TYPE any CODE`, + err: "Found `` but expected `js/lua script`", + }, + { + sql: `DEFINE FIELD temp ON person TYPE any CODE "return doc.data.id"`, + res: &Query{Statements: []Statement{&DefineFieldStatement{ + Name: "temp", + What: []string{"person"}, + Type: "any", + Code: "return doc.data.id", + }}}, + }, + { + sql: `DEFINE FIELD temp ON person TYPE any CODE something`, + err: "Found `something` but expected `js/lua script`", + }, + { + sql: `DEFINE FIELD temp ON person TYPE any MATCH`, + err: "Found `` but expected `regular expression`", + }, + { + sql: `DEFINE FIELD temp ON person TYPE any MATCH /.*/`, + res: &Query{Statements: []Statement{&DefineFieldStatement{ + Name: "temp", + What: []string{"person"}, + Type: "any", + Match: ".*", + }}}, + }, + { + sql: `DEFINE FIELD temp ON person TYPE any MATCH something`, + err: "Found `something` but expected `regular expression`", + }, { sql: `DEFINE FIELD temp ON person TYPE any NOTNULL`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Notnull: true, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any NOTNULL true`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Notnull: true, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any NOTNULL false`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Notnull: false, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any READONLY`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Readonly: true, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any READONLY true`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Readonly: true, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any READONLY false`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Readonly: false, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any MANDATORY`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Mandatory: true, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any MANDATORY true`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Mandatory: true, }}}, }, { sql: `DEFINE FIELD temp ON person TYPE any MANDATORY false`, res: &Query{Statements: []Statement{&DefineFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Type: Ident("any"), + Name: "temp", + What: []string{"person"}, + Type: "any", Mandatory: false, }}}, }, + { + sql: `DEFINE FIELD temp ON person TYPE any VALIDATE`, + res: &Query{Statements: []Statement{&DefineFieldStatement{ + Name: "temp", + What: []string{"person"}, + Type: "any", + Validate: true, + }}}, + }, + { + sql: `DEFINE FIELD temp ON person TYPE any VALIDATE true`, + res: &Query{Statements: []Statement{&DefineFieldStatement{ + Name: "temp", + What: []string{"person"}, + Type: "any", + Validate: true, + }}}, + }, + { + sql: `DEFINE FIELD temp ON person TYPE any VALIDATE false`, + res: &Query{Statements: []Statement{&DefineFieldStatement{ + Name: "temp", + What: []string{"person"}, + Type: "any", + Validate: false, + }}}, + }, { sql: `DEFINE FIELD temp ON person TYPE any something`, err: "Found `something` but expected `EOF, ;`", @@ -1499,57 +1652,31 @@ func Test_Parse_Queries_Define(t *testing.T) { }, { sql: `DEFINE INDEX temp ON`, - err: "Found `` but expected `table name`", + err: "Found `` but expected `name`", }, { sql: `DEFINE INDEX temp ON person`, - err: "Found `` but expected `CODE, COLUMNS`", - }, - { - sql: `DEFINE INDEX temp ON person CODE`, - err: "Found `` but expected `LUA script`", - }, - { - sql: `DEFINE INDEX temp ON person CODE ""`, - res: &Query{Statements: []Statement{&DefineIndexStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Code: "", - }}}, - }, - { - sql: `DEFINE INDEX temp ON person CODE "\nemit()\n"`, - res: &Query{Statements: []Statement{&DefineIndexStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Code: "\nemit()\n", - }}}, + err: "Found `` but expected `COLUMNS`", }, { sql: `DEFINE INDEX temp ON person COLUMNS`, - err: "Found `` but expected `field name`", + err: "Found `` but expected `name`", }, { sql: `DEFINE INDEX temp ON person COLUMNS firstname, lastname`, res: &Query{Statements: []Statement{&DefineIndexStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Cols: []*Field{ - {Expr: Ident("firstname")}, - {Expr: Ident("lastname")}, - }, + Name: "temp", + What: []string{"person"}, + Cols: []string{"firstname", "lastname"}, Uniq: false, }}}, }, { sql: `DEFINE INDEX temp ON person COLUMNS firstname, lastname UNIQUE`, res: &Query{Statements: []Statement{&DefineIndexStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, - Cols: []*Field{ - {Expr: Ident("firstname")}, - {Expr: Ident("lastname")}, - }, + Name: "temp", + What: []string{"person"}, + Cols: []string{"firstname", "lastname"}, Uniq: true, }}}, }, @@ -1582,12 +1709,12 @@ func Test_Parse_Queries_Resync(t *testing.T) { }, { sql: `RESYNC INDEX ON`, - err: "Found `` but expected `table name`", + err: "Found `` but expected `name`", }, { sql: `RESYNC INDEX ON person`, res: &Query{Statements: []Statement{&ResyncIndexStatement{ - What: []Table{Table("person")}, + What: []string{"person"}, }}}, }, { @@ -1607,20 +1734,45 @@ func Test_Parse_Queries_Remove(t *testing.T) { var tests = []tester{ { sql: `REMOVE`, - err: "Found `` but expected `TABLE, FIELD, INDEX`", + err: "Found `` but expected `RULES, TABLE, FIELD, INDEX`", }, + // ---------------------------------------------------------------------- { sql: `REMOVE TABLE`, - err: "Found `` but expected `table name`", + err: "Found `` but expected `name`", }, { sql: `REMOVE TABLE person`, res: &Query{Statements: []Statement{&RemoveTableStatement{ - What: []Table{Table("person")}, + What: []string{"person"}, + }}}, + }, + // ---------------------------------------------------------------------- + { + sql: `REMOVE RULES`, + err: "Found `` but expected `ON`", + }, + { + sql: `REMOVE RULES ON`, + err: "Found `` but expected `name`", + }, + { + sql: `REMOVE RULES ON person`, + err: "Found `` but expected `FOR`", + }, + { + sql: `REMOVE RULES ON person FOR`, + err: "Found `` but expected `SELECT, CREATE, UPDATE, DELETE, RELATE, RECORD`", + }, + { + sql: `REMOVE RULES ON person FOR select, create, update, delete`, + res: &Query{Statements: []Statement{&RemoveRulesStatement{ + What: []string{"person"}, + When: []string{"SELECT", "CREATE", "UPDATE", "DELETE"}, }}}, }, { - sql: `REMOVE TABLE person something`, + sql: `REMOVE RULES ON person FOR select, create, update, delete something`, err: "Found `something` but expected `EOF, ;`", }, // ---------------------------------------------------------------------- @@ -1634,13 +1786,13 @@ func Test_Parse_Queries_Remove(t *testing.T) { }, { sql: `REMOVE FIELD temp ON`, - err: "Found `` but expected `table name`", + err: "Found `` but expected `name`", }, { sql: `REMOVE FIELD temp ON person`, res: &Query{Statements: []Statement{&RemoveFieldStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, + Name: "temp", + What: []string{"person"}, }}}, }, { @@ -1658,13 +1810,13 @@ func Test_Parse_Queries_Remove(t *testing.T) { }, { sql: `REMOVE INDEX temp ON`, - err: "Found `` but expected `table name`", + err: "Found `` but expected `name`", }, { sql: `REMOVE INDEX temp ON person`, res: &Query{Statements: []Statement{&RemoveIndexStatement{ - Name: Ident("temp"), - What: []Table{Table("person")}, + Name: "temp", + What: []string{"person"}, }}}, }, { @@ -1678,3 +1830,84 @@ func Test_Parse_Queries_Remove(t *testing.T) { } } + +func Test_Parse_Queries_Begin(t *testing.T) { + + var tests = []tester{ + { + sql: `BEGIN`, + res: &Query{Statements: []Statement{&BeginStatement{}}}, + }, + { + sql: `BEGIN something`, + err: "Found `something` but expected `EOF, ;`", + }, + { + sql: `BEGIN TRANSACTION`, + res: &Query{Statements: []Statement{&BeginStatement{}}}, + }, + { + sql: `BEGIN TRANSACTION something`, + err: "Found `something` but expected `EOF, ;`", + }, + } + + for _, test := range tests { + testsql(t, test) + } + +} + +func Test_Parse_Queries_Cancel(t *testing.T) { + + var tests = []tester{ + { + sql: `CANCEL`, + res: &Query{Statements: []Statement{&CancelStatement{}}}, + }, + { + sql: `CANCEL something`, + err: "Found `something` but expected `EOF, ;`", + }, + { + sql: `CANCEL TRANSACTION`, + res: &Query{Statements: []Statement{&CancelStatement{}}}, + }, + { + sql: `CANCEL TRANSACTION something`, + err: "Found `something` but expected `EOF, ;`", + }, + } + + for _, test := range tests { + testsql(t, test) + } + +} + +func Test_Parse_Queries_Commit(t *testing.T) { + + var tests = []tester{ + { + sql: `Commit`, + res: &Query{Statements: []Statement{&CommitStatement{}}}, + }, + { + sql: `Commit something`, + err: "Found `something` but expected `EOF, ;`", + }, + { + sql: `Commit TRANSACTION`, + res: &Query{Statements: []Statement{&CommitStatement{}}}, + }, + { + sql: `Commit TRANSACTION something`, + err: "Found `something` but expected `EOF, ;`", + }, + } + + for _, test := range tests { + testsql(t, test) + } + +} diff --git a/sql/table.go b/sql/table.go index ea4f4f43..48dde9e3 100644 --- a/sql/table.go +++ b/sql/table.go @@ -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 } diff --git a/sql/token.go b/sql/token.go index 1be693ad..953f8e4d 100644 --- a/sql/token.go +++ b/sql/token.go @@ -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 diff --git a/sql/trans.go b/sql/trans.go new file mode 100644 index 00000000..4961ae8d --- /dev/null +++ b/sql/trans.go @@ -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 + +} diff --git a/sql/type.go b/sql/type.go index 18c577fa..19a64b86 100644 --- a/sql/type.go +++ b/sql/type.go @@ -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 } diff --git a/sql/util.go b/sql/util.go index 8dd6a519..ae7eb084 100644 --- a/sql/util.go +++ b/sql/util.go @@ -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)