Add initial sql parser code

This commit is contained in:
Tobie Morgan Hitchcock 2016-02-26 17:27:07 +00:00
parent 7501d7c78b
commit 89b71cbe72
25 changed files with 3166 additions and 0 deletions

267
sql/ast.go Normal file
View file

@ -0,0 +1,267 @@
// 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
import (
"regexp"
"time"
)
// --------------------------------------------------
// Queries
// --------------------------------------------------
// Query represents a multi statement SQL query
type Query struct {
Statements Statements
}
// Statement represents a single SQL AST
type Statement interface{}
// Statements represents multiple SQL ASTs
type Statements []Statement
// --------------------------------------------------
// Select
// --------------------------------------------------
// SelectStatement represents a SQL SELECT statement.
type SelectStatement struct {
Fields []*Field
Thing Expr
Where Expr
Group []*Group
Order []*Order
Limit Expr
Start Expr
Version Expr
}
// --------------------------------------------------
// Items
// --------------------------------------------------
// CreateStatement represents a SQL CREATE statement.
// CREATE person SET column = 'value'
type CreateStatement struct {
What Expr
Data Expr
}
// InsertStatement represents a SQL INSERT statement.
// INSERT person SET column = 'value'
type InsertStatement struct {
What Expr
Data Expr
}
// UpsertStatement represents a SQL UPSERT statement.
// UPSERT @person:123 SET column = 'value'
type UpsertStatement struct {
What Expr
Data Expr
}
// UpdateStatement represents a SQL UPDATE statement.
// UPDATE person SET column = 'value'
type UpdateStatement struct {
Thing Expr
Data Expr
Where Expr
}
// DeleteStatement represents a SQL DELETE statement.
// DELETE FROMn person
type DeleteStatement struct {
Thing Expr
Where Expr
}
// RelateStatement represents a SQL RELATE statement.
// RELATE friend FROM @person:123 TO @person:456 SET column = 'value'
type RelateStatement struct {
Kind Expr
From *Thing
To *Thing
Data Expr
}
// RecordStatement represents a SQL CREATE EVENT statement.
// RECORD login ON @person:123 AT 2016-01-29T22:42:56.478Z SET column = true
type RecordStatement struct {
Name Expr
ON *Thing
At Expr
Data Expr
}
// --------------------------------------------------
// Index
// --------------------------------------------------
// DefineIndexStatement represents an SQL DEFINE INDEX statement.
// DEFINE INDEX name ON person COLUMNS (account, age) UNIQUE
type DefineIndexStatement struct {
Index Expr
Table Expr
Fields []*Field
Unique Expr
}
// ResyncIndexStatement represents an SQL RESYNC INDEX statement.
// RESYNC INDEX name ON person
type ResyncIndexStatement struct {
Index Expr
Table Expr
}
// RemoveIndexStatement represents an SQL REMOVE INDEX statement.
// REMOVE INDEX name ON person
type RemoveIndexStatement struct {
Index Expr
Table Expr
}
// --------------------------------------------------
// Views
// --------------------------------------------------
// DefineViewStatement represents an SQL DEFINE VIEW statement.
// DEFINE VIEW name MAP `` REDUCE ``
type DefineViewStatement struct {
View Expr
Map Expr
Reduce Expr
}
// ResyncViewStatement represents an SQL RESYNC VIEW statement.
// RESYNC VIEW name
type ResyncViewStatement struct {
View Expr
}
// RemoveViewStatement represents an SQL REMOVE VIEW statement.
// REMOVE VIEW name
type RemoveViewStatement struct {
View Expr
}
// --------------------------------------------------
// Literals
// --------------------------------------------------
// Expr represents a sql expression
type Expr interface{}
// Null represents a null expression.
type Null struct{}
// Wildcard represents a wildcard expression.
type Wildcard struct{}
// IdentLiteral represents a variable.
type IdentLiteral struct {
Val string `json:"Ident"`
}
// JSONLiteral represents a regular expression.
type JSONLiteral struct {
Val interface{} `json:"Json"`
}
// RegexLiteral represents a regular expression.
type RegexLiteral struct {
Val *regexp.Regexp `json:"Regex"`
}
// NumberLiteral represents a integer literal.
type NumberLiteral struct {
Val int64 `json:"Number"`
}
// DoubleLiteral represents a float literal.
type DoubleLiteral struct {
Val float64 `json:"Double"`
}
// StringLiteral represents a string literal.
type StringLiteral struct {
Val string `json:"String"`
}
// BooleanLiteral represents a boolean literal.
type BooleanLiteral struct {
Val bool `json:"Boolean"`
}
// DatetimeLiteral represents a point-in-time literal.
type DatetimeLiteral struct {
Val time.Time `json:"Datetime"`
}
// DurationLiteral represents a duration literal.
type DurationLiteral struct {
Val time.Duration `json:"Duration"`
}
// DirectionLiteral represents a duration literal.
type DirectionLiteral struct {
Val bool `json:"Direction"`
}
// ClosedExpression represents a parenthesized expression.
type ClosedExpression struct {
Expr Expr
}
// BinaryExpression represents a binary expression tree,
type BinaryExpression struct {
LHS Expr
Op string
RHS Expr
}
// --------------------------------------------------
// Parts
// --------------------------------------------------
// Table comment
type Table struct {
Name string `json:"Name"`
}
// Thing comment
type Thing struct {
Table string `json:"Table"`
ID string `json:"ID"`
}
// Field comment
type Field struct {
Expr Expr
Alias Expr
}
// Group comment
type Group struct {
Expr Expr
}
// Order comment
type Order struct {
Expr Expr
Dir Expr
}

48
sql/create.go Normal file
View file

@ -0,0 +1,48 @@
// 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) parseCreateStatement() (*CreateStatement, error) {
stmt := &CreateStatement{}
var err error
// Next token might be INTO
_, _, _ = p.mightBe(INTO)
// Parse table name
if stmt.What, err = p.parseTable(); err != nil {
return nil, err
}
// Next token should be SET
if _, _, err = p.shouldBe(SET); err != nil {
return nil, err
}
// Parse data set
if stmt.Data, err = p.parseSet(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}

31
sql/define.go Normal file
View file

@ -0,0 +1,31 @@
// 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) parseDefineStatement() (Statement, error) {
// Inspect the next token.
tok, lit := p.scanIgnoreWhitespace()
switch tok {
case VIEW:
return p.parseDefineViewStatement()
case INDEX:
return p.parseDefineIndexStatement()
default:
return nil, &ParseError{Found: lit, Expected: []string{"INDEX", "VIEW"}}
}
}

19
sql/delete.go Normal file
View file

@ -0,0 +1,19 @@
// 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) parseDeleteStatement() (Statement, error) {
return nil, nil
}

15
sql/doc.go Normal file
View file

@ -0,0 +1,15 @@
// 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

31
sql/error.go Normal file
View file

@ -0,0 +1,31 @@
// 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
import (
"fmt"
"strings"
)
// ParseError represents an error that occurred during parsing.
type ParseError struct {
Found string
Expected []string
}
// Error returns the string representation of the error.
func (e *ParseError) Error() string {
return fmt.Sprintf("found `%s` but expected `%s`", e.Found, strings.Join(e.Expected, ", "))
}

139
sql/exprs.go Normal file
View file

@ -0,0 +1,139 @@
// 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) parseTable() (*Table, error) {
_, lit, err := p.shouldBe(IDENT)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"table name"}}
}
return &Table{Name: lit}, nil
}
func (p *Parser) parseIdent() (*IdentLiteral, error) {
_, lit, err := p.shouldBe(IDENT)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"name"}}
}
return &IdentLiteral{Val: lit}, nil
}
func (p *Parser) parseString() (*StringLiteral, error) {
_, lit, err := p.shouldBe(STRING)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"string"}}
}
return &StringLiteral{Val: lit}, nil
}
func (p *Parser) parseRegion() (*StringLiteral, error) {
_, lit, err := p.shouldBe(IDENT, STRING, REGION)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"string"}}
}
return &StringLiteral{Val: lit}, nil
}
func (p *Parser) parseFields() ([]*Field, error) {
var m []*Field
for {
s, err := p.parseField()
if err != nil {
return nil, err
}
m = append(m, s)
// If the next token is not a comma then break the loop.
if _, _, exi := p.mightBe(COMMA); !exi {
break
}
}
return m, nil
}
func (p *Parser) parseField() (*Field, error) {
f := &Field{}
expr, err := p.parseExpr()
if err != nil {
return nil, err
}
f.Expr = expr
switch expr.(type) {
case *Wildcard:
return f, nil
}
alias, err := p.parseAlias()
if err != nil {
return nil, err
}
f.Alias = alias
return f, nil
}
func (p *Parser) parseExpr() (ex Expr, er error) {
tok, lit, err := p.shouldBe(IDENT, NULL, ALL, TIME, TRUE, FALSE, STRING, REGION, NUMBER, DOUBLE, JSON)
if err != nil {
er = &ParseError{Found: lit, Expected: []string{"field name"}}
}
ex = declare(tok, lit)
return
}
func (p *Parser) parseAlias() (ex Expr, er error) {
if _, _, exi := p.mightBe(AS); !exi {
return nil, nil
}
tok, lit, err := p.shouldBe(IDENT)
if err != nil {
er = &ParseError{Found: lit, Expected: []string{"field name"}}
}
ex = declare(tok, lit)
return
}

118
sql/index.go Normal file
View file

@ -0,0 +1,118 @@
// 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) parseDefineIndexStatement() (*DefineIndexStatement, error) {
stmt := &DefineIndexStatement{}
var err error
// Parse index name
if stmt.Index, err = p.parseIdent(); err != nil {
return nil, err
}
// Next token should be ON
if _, _, err = p.shouldBe(ON); err != nil {
return nil, err
}
// Parse table name
if stmt.Table, err = p.parseTable(); err != nil {
return nil, err
}
// Next token should be COLUMNS
if _, _, err = p.shouldBe(COLUMNS); err != nil {
return nil, err
}
// Parse columns
if stmt.Fields, err = p.parseFields(); err != nil {
return nil, err
}
// Parse unique
_, _, stmt.Unique = p.mightBe(UNIQUE)
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}
func (p *Parser) parseResyncIndexStatement() (*ResyncIndexStatement, error) {
stmt := &ResyncIndexStatement{}
var err error
// Parse index name
if stmt.Index, err = p.parseIdent(); err != nil {
return nil, err
}
// Next token should be ON
if _, _, err = p.shouldBe(ON); err != nil {
return nil, err
}
// Parse table name
if stmt.Table, err = p.parseTable(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}
func (p *Parser) parseRemoveIndexStatement() (*RemoveIndexStatement, error) {
stmt := &RemoveIndexStatement{}
var err error
// Parse index name
if stmt.Index, err = p.parseIdent(); err != nil {
return nil, err
}
// Next token should be ON
if _, _, err = p.shouldBe(ON); err != nil {
return nil, err
}
// Parse table name
if stmt.Table, err = p.parseTable(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}

48
sql/insert.go Normal file
View file

@ -0,0 +1,48 @@
// 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) parseInsertStatement() (*InsertStatement, error) {
stmt := &InsertStatement{}
var err error
// Next token might be INTO
_, _, _ = p.mightBe(INTO)
// Parse table name
if stmt.What, err = p.parseTable(); err != nil {
return nil, err
}
// Next token should be SET
if _, _, err = p.shouldBe(SET); err != nil {
return nil, err
}
// Parse data set
if stmt.Data, err = p.parseSet(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}

19
sql/modify.go Normal file
View file

@ -0,0 +1,19 @@
// 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) parseModifyStatement() (Statement, error) {
return nil, nil
}

183
sql/parser.go Normal file
View file

@ -0,0 +1,183 @@
// 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
import (
"io"
"strings"
)
// Parser represents a parser.
type Parser struct {
s *Scanner
buf struct {
tok Token // last read token
lit string // last read literal
n int // buffer size (max=1)
}
}
// Parse parses a string.
func Parse(s string) (*Query, error) {
r := strings.NewReader(s)
p := &Parser{s: NewScanner(r)}
return p.Parse()
}
// NewParser returns a new instance of Parser.
func NewParser(r io.Reader) *Parser {
return &Parser{s: NewScanner(r)}
}
// Parse parses single or multiple SQL queries.
func (p *Parser) Parse() (*Query, error) {
return p.ParseMulti()
}
// ParseMulti parses multiple SQL SELECT statements.
func (p *Parser) ParseMulti() (*Query, error) {
var statements Statements
var semi bool
for {
if tok, _ := p.scanIgnoreWhitespace(); tok == EOF {
return &Query{Statements: statements}, nil
} else if !semi && tok == SEMICOLON {
semi = true
} else {
p.unscan()
s, err := p.ParseSingle()
if err != nil {
return nil, err
}
statements = append(statements, s)
semi = false
}
}
}
// ParseSingle parses a single SQL SELECT statement.
func (p *Parser) ParseSingle() (Statement, error) {
// Inspect the first token.
tok, lit := p.scanIgnoreWhitespace()
switch tok {
case SELECT:
return p.parseSelectStatement()
case CREATE:
return p.parseCreateStatement()
case INSERT:
return p.parseInsertStatement()
case UPSERT:
return p.parseUpsertStatement()
case UPDATE:
return p.parseUpdateStatement()
case MODIFY:
return p.parseModifyStatement()
case DELETE:
return p.parseDeleteStatement()
case RELATE:
return p.parseRelateStatement()
case RECORD:
return p.parseRecordStatement()
case DEFINE:
return p.parseDefineStatement()
case RESYNC:
return p.parseResyncStatement()
case REMOVE:
return p.parseRemoveStatement()
default:
return nil, &ParseError{
Found: lit,
Expected: []string{
"SELECT",
"INSERT",
"UPSERT",
"UPDATE",
"MODIFY",
"DELETE",
"RELATE",
"RECORD",
"DEFINE",
"RESYNC",
"REMOVE",
},
}
}
}
func (p *Parser) mightBe(expected ...Token) (tok Token, lit string, found bool) {
tok, lit = p.scanIgnoreWhitespace()
if found = in(tok, expected); !found {
p.unscan()
}
return
}
func (p *Parser) shouldBe(expected ...Token) (tok Token, lit string, err error) {
tok, lit = p.scanIgnoreWhitespace()
if found := in(tok, expected); !found {
p.unscan()
err = &ParseError{Found: lit, Expected: lookup(expected)}
}
return
}
// scan returns the next token from the underlying scanner.
// If a token has been unscanned then read that instead.
func (p *Parser) scan() (tok Token, lit string) {
// If we have a token on the buffer, then return it.
if p.buf.n != 0 {
p.buf.n = 0
return p.buf.tok, p.buf.lit
}
// Otherwise read the next token from the scanner.
tok, lit = p.s.Scan()
// Save it to the buffer in case we unscan later.
p.buf.tok, p.buf.lit = tok, lit
return
}
// unscan pushes the previously read token back onto the buffer.
func (p *Parser) unscan() { p.buf.n = 1 }
// scanIgnoreWhitespace scans the next non-whitespace token.
func (p *Parser) scanIgnoreWhitespace() (tok Token, lit string) {
tok, lit = p.scan()
if tok == WS {
tok, lit = p.scan()
}
return
}

19
sql/record.go Normal file
View file

@ -0,0 +1,19 @@
// 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) parseRecordStatement() (Statement, error) {
return nil, nil
}

19
sql/relate.go Normal file
View file

@ -0,0 +1,19 @@
// 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) parseRelateStatement() (Statement, error) {
return nil, nil
}

31
sql/remove.go Normal file
View file

@ -0,0 +1,31 @@
// 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) parseRemoveStatement() (Statement, error) {
// Inspect the next token.
tok, lit := p.scanIgnoreWhitespace()
switch tok {
case VIEW:
return p.parseRemoveViewStatement()
case INDEX:
return p.parseRemoveIndexStatement()
default:
return nil, &ParseError{Found: lit, Expected: []string{"INDEX", "VIEW"}}
}
}

31
sql/resync.go Normal file
View file

@ -0,0 +1,31 @@
// 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) parseResyncStatement() (Statement, error) {
// Inspect the next token.
tok, lit := p.scanIgnoreWhitespace()
switch tok {
case VIEW:
return p.parseResyncViewStatement()
case INDEX:
return p.parseResyncIndexStatement()
default:
return nil, &ParseError{Found: lit, Expected: []string{"INDEX", "VIEW"}}
}
}

405
sql/scanner.go Normal file
View file

@ -0,0 +1,405 @@
// 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
import (
"bufio"
"bytes"
"encoding/json"
"io"
"strconv"
"strings"
"time"
)
// Scanner represents a lexical scanner.
type Scanner struct {
r *bufio.Reader
}
// NewScanner returns a new instance of Scanner.
func NewScanner(r io.Reader) *Scanner {
return &Scanner{r: bufio.NewReader(r)}
}
// Scan returns the next token and literal value.
func (s *Scanner) Scan() (tok Token, lit string) {
// Read the next rune.
ch := s.read()
// If we see whitespace then consume all contiguous whitespace.
if isWhitespace(ch) {
s.unread()
return s.scanWhitespace()
}
// If we see a letter then consume as an string.
if isLetter(ch) {
s.unread()
return s.scanIdent()
}
// If we see a number then consume as a number.
if isNumber(ch) {
s.unread()
return s.scanNumber()
}
// Otherwise read the individual character.
switch ch {
case eof:
return EOF, ""
case '*':
return ALL, string(ch)
case '@':
return EAT, string(ch)
case ',':
return COMMA, string(ch)
case '.':
chn := s.read()
s.unread()
if isNumber(chn) {
return s.scanNumber()
}
return DOT, string(ch)
case '"':
s.unread()
return s.scanString()
case '\'':
s.unread()
return s.scanString()
case '`':
s.unread()
return s.scanQuoted()
case '{':
s.unread()
return s.scanSpecial()
case '[':
s.unread()
return s.scanSpecial()
case ':':
return COLON, string(ch)
case ';':
return SEMICOLON, string(ch)
case '(':
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 DEC, "-="
}
s.unread()
return SUB, string(ch)
case '!':
if chn := s.read(); chn == '=' {
return NEQ, "!="
}
s.unread()
case '<':
if chn := s.read(); chn == '=' {
return LTE, "<="
}
s.unread()
return LT, string(ch)
case '>':
if chn := s.read(); chn == '=' {
return GTE, ">="
}
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) {
// Create a buffer and read the current character into it.
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.
for {
if ch := s.read(); ch == eof {
break
} else if !isWhitespace(ch) {
s.unread()
break
} else {
buf.WriteRune(ch)
}
}
return WS, buf.String()
}
// scanIdent consumes the current rune and all contiguous ident runes.
func (s *Scanner) scanIdent() (tok Token, lit string) {
// Create a buffer and read the current character into it.
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.
for {
if ch := s.read(); ch == eof {
break
} else if !isIdentChar(ch) {
s.unread()
break
} else {
buf.WriteRune(ch)
}
}
// If the string matches a keyword then return that keyword.
if tok := keywords[strings.ToUpper(buf.String())]; tok > 0 {
return tok, buf.String()
}
// Otherwise return as a regular identifier.
return IDENT, buf.String()
}
func (s *Scanner) scanQuoted() (Token, string) {
tok, lit := s.scanString()
if is(tok, STRING) {
return IDENT, lit
}
return tok, lit
}
func (s *Scanner) scanString() (tok Token, lit string) {
tok = STRING
var buf bytes.Buffer
char := s.read()
for {
if ch := s.read(); ch == char {
break
} else if ch == eof {
return ILLEGAL, buf.String()
} else if ch == '\n' {
tok = REGION
buf.WriteRune(ch)
} else if ch == '\\' {
chn := s.read()
if chn == 'n' {
buf.WriteRune('\n')
} else if chn == '\\' {
buf.WriteRune('\\')
} else if chn == '"' {
buf.WriteRune('"')
} else if chn == '\'' {
buf.WriteRune('\'')
} else if chn == '`' {
buf.WriteRune('`')
} else {
return ILLEGAL, buf.String()
}
} else {
buf.WriteRune(ch)
}
}
return tok, buf.String()
}
func (s *Scanner) scanNumber() (tok Token, lit string) {
tok = NUMBER
// Create a buffer and read the current character into it.
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.
for {
if ch := s.read(); ch == eof {
break
} else if ch == '.' {
if tok == DOUBLE {
tok = IDENT
}
if tok == NUMBER {
tok = DOUBLE
}
buf.WriteRune(ch)
} else if ch == '+' {
buf.WriteRune(ch)
} else if ch == '-' {
buf.WriteRune(ch)
} else if ch == ':' {
buf.WriteRune(ch)
} else if ch == 'T' {
buf.WriteRune(ch)
} else if isLetter(ch) {
tok = IDENT
buf.WriteRune(ch)
} else if !isNumber(ch) {
s.unread()
break
} else {
buf.WriteRune(ch)
}
}
if _, err := time.Parse("2006-01-02", buf.String()); err == nil {
return DATE, buf.String()
}
if _, err := time.Parse(time.RFC3339, buf.String()); err == nil {
return TIME, buf.String()
}
if _, err := time.Parse(time.RFC3339Nano, buf.String()); err == nil {
return NANO, buf.String()
}
return tok, buf.String()
}
func (s *Scanner) scanSpecial() (tok Token, lit string) {
tok = IDENT
var buf bytes.Buffer
beg := s.read()
end := beg
if beg == '{' {
end = '}'
}
if beg == '[' {
end = ']'
}
for {
if ch := s.read(); ch == end {
break
} else if ch == eof {
return ILLEGAL, buf.String()
} else if ch == '\n' {
tok = REGION
buf.WriteRune(ch)
} else if ch == '\\' {
chn := s.read()
if chn == 'n' {
tok = REGION
buf.WriteRune('\n')
} else if chn == '\\' {
buf.WriteRune('\\')
} else if chn == '"' {
buf.WriteRune('"')
} else if chn == '\'' {
buf.WriteRune('\'')
} else if chn == '`' {
buf.WriteRune('`')
} else {
break
}
} else {
buf.WriteRune(ch)
}
}
var f interface{}
j := []byte(string(beg) + buf.String() + string(end))
err := json.Unmarshal(j, &f)
if err == nil {
return JSON, string(beg) + buf.String() + string(end)
}
return tok, buf.String()
}
// read 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()
if err != nil {
return eof
}
return ch
}
// unread places the previously read rune back on the reader.
func (s *Scanner) unread() {
_ = s.r.UnreadRune()
}
func number(lit string) (i int64) {
i, _ = strconv.ParseInt(lit, 10, 64)
return
}
// isWhitespace returns true if the rune is a space, tab, or newline.
func isWhitespace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n'
}
// 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 == '.')
}
// isIdentChar returns true if the rune can be used in an unquoted identifier.
func isIdentChar(ch rune) bool {
return isLetter(ch) || isNumber(ch) || isSeparator(ch) || ch == '_'
}
// eof represents a marker rune for the end of the reader.
var eof = rune(0)

278
sql/select.go Normal file
View file

@ -0,0 +1,278 @@
// 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) parseSelectStatement() (*SelectStatement, error) {
stmt := &SelectStatement{}
var err error
if stmt.Fields, err = p.parseFields(); err != nil {
return nil, err
}
// Next token should be FROM
_, _, err = p.shouldBe(FROM)
if err != nil {
return nil, err
}
if stmt.Thing, err = p.parseThings(); err != nil {
return nil, err
}
if stmt.Where, err = p.parseWhere(); err != nil {
return nil, err
}
if stmt.Group, err = p.parseGroup(); err != nil {
return nil, err
}
if stmt.Order, err = p.parseOrder(); err != nil {
return nil, err
}
if stmt.Limit, err = p.parseLimit(); err != nil {
return nil, err
}
if stmt.Start, err = p.parseStart(); err != nil {
return nil, err
}
if stmt.Version, err = p.parseVersion(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}
func (p *Parser) parseWhere() (Expr, error) {
var ws []Expr
var tok Token
var lit string
var err error
// Remove the WHERE keyword
if _, _, exi := p.mightBe(WHERE); !exi {
return nil, nil
}
for {
w := &BinaryExpression{}
tok, lit, err = p.shouldBe(IDENT, TIME, TRUE, FALSE, STRING, NUMBER, DOUBLE)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
}
w.LHS = declare(tok, lit)
tok, lit, err = p.shouldBe(IN, EQ, NEQ, GT, LT, GTE, LTE, EQR, NER)
if err != nil {
return nil, err
}
w.Op = lit
tok, lit, err = p.shouldBe(IDENT, NULL, TIME, TRUE, FALSE, STRING, NUMBER, DOUBLE, REGEX, JSON)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field value"}}
}
w.RHS = declare(tok, lit)
ws = append(ws, w)
// Remove the WHERE keyword
if _, _, exi := p.mightBe(AND, OR); !exi {
break
}
}
return ws, nil
}
func (p *Parser) parseGroup() ([]*Group, error) {
var gs []*Group
// Remove the GROUP keyword
if _, _, exi := p.mightBe(GROUP); !exi {
return nil, nil
}
// Next token might be BY
_, _, _ = p.mightBe(BY)
return gs, nil
}
func (p *Parser) parseOrder() ([]*Order, error) {
var m []*Order
var tok Token
var lit string
var err error
var exi bool
// Remove the ORDER keyword
if _, _, exi := p.mightBe(ORDER); !exi {
return nil, nil
}
// Next token might be BY
_, _, _ = p.mightBe(BY)
for {
s := &Order{}
tok, lit, err = p.shouldBe(IDENT)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
}
s.Expr = declare(tok, lit)
tok, lit, exi = p.mightBe(ASC, DESC)
if !exi {
tok = ASC
lit = "ASC"
}
s.Dir = declare(tok, lit)
m = append(m, s)
// If the next token is not a comma then break the loop.
if _, _, exi := p.mightBe(COMMA); !exi {
break
}
}
return m, nil
}
func (p *Parser) parseLimit() (Expr, error) {
// Remove the LIMIT keyword
if _, _, exi := p.mightBe(LIMIT); !exi {
return nil, nil
}
// Next token might be BY
_, _, _ = p.mightBe(BY)
_, lit, err := p.shouldBe(NUMBER)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"limit number"}}
}
return &NumberLiteral{Val: number(lit)}, nil
}
func (p *Parser) parseStart() (Expr, error) {
var tok Token
var lit string
var err error
// Remove the START keyword
if _, _, exi := p.mightBe(START); !exi {
return nil, nil
}
// Next token might be AT
_, _, _ = p.mightBe(AT)
// Next token might be @
_, _, exi := p.mightBe(EAT)
if exi == false {
// Parse table name
tok, lit = p.scan()
if !is(tok, NUMBER) {
p.unscan()
return nil, &ParseError{Found: lit, Expected: []string{"table name"}}
}
return &NumberLiteral{Val: number(lit)}, nil
}
if exi == true {
t := &Thing{}
// Parse table name
tok, lit = p.scan()
if !is(tok, IDENT, NUMBER) {
p.unscan()
return nil, &ParseError{Found: lit, Expected: []string{"table name"}}
}
t.Table = lit
// Next token should be :
_, _, err = p.shouldBe(COLON)
if err != nil {
return nil, err
}
// Parse table id
tok, lit = p.scan()
if !is(tok, IDENT, NUMBER) {
p.unscan()
return nil, &ParseError{Found: lit, Expected: []string{"table id"}}
}
t.ID = lit
return t, nil
}
return nil, nil
}
func (p *Parser) parseVersion() (Expr, error) {
// Remove the VERSION keyword
if _, _, exi := p.mightBe(VERSION); !exi {
return nil, nil
}
tok, lit, err := p.shouldBe(DATE, TIME, NANO)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"timestamp"}}
}
return declare(tok, lit), nil
}

59
sql/set.go Normal file
View file

@ -0,0 +1,59 @@
// 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) parseSet() ([]Expr, error) {
var mul []Expr
var tok Token
var lit string
var err error
for {
one := &BinaryExpression{}
tok, lit, err = p.shouldBe(IDENT)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field name"}}
}
one.LHS = declare(tok, lit)
tok, lit, err = p.shouldBe(EQ, INC, DEC)
if err != nil {
return nil, err
}
one.Op = lit
tok, lit, err = p.shouldBe(IDENT, TIME, TRUE, FALSE, STRING, NUMBER, DOUBLE, JSON)
if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"field value"}}
}
one.RHS = declare(tok, lit)
mul = append(mul, one)
// If the next token is not a comma then break the loop.
if _, _, exi := p.mightBe(COMMA); !exi {
p.unscan()
break
}
}
return mul, nil
}

745
sql/sql_test.go Normal file
View file

@ -0,0 +1,745 @@
// 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_test
import (
"strings"
"testing"
"github.com/abcum/surreal/sql"
. "github.com/smartystreets/goconvey/convey"
)
type tester struct {
skip bool
sql string
err string
res sql.Statement
}
func testerr(err error) string {
if err != nil {
return err.Error()
}
return ""
}
func testsql(t *testing.T, test tester) {
s, e := sql.NewParser(strings.NewReader(test.sql)).Parse()
if test.skip {
Convey(" ❗️ "+test.sql, t, nil)
return
}
Convey(test.sql, t, func() {
if test.err == "" {
So(e, ShouldBeNil)
So(s, ShouldResemble, test.res)
}
if test.err != "" {
Convey(testerr(e), func() {
So(testerr(e), ShouldResemble, test.err)
})
}
})
}
// Ensure the parser can parse a multi-statement query.
func Test_Parse_General(t *testing.T) {
s := `SELECT a FROM b`
q, err := sql.Parse(s)
if err != nil {
t.Fatalf("unexpected error: %s", err)
} else if len(q.Statements) != 1 {
t.Fatalf("unexpected statement count: %d", len(q.Statements))
}
}
// Ensure the parser can parse a multi-statement query.
func Test_Parse_General_Single(t *testing.T) {
s := `SELECT a FROM b`
q, err := sql.NewParser(strings.NewReader(s)).Parse()
if err != nil {
t.Fatalf("unexpected error: %s", err)
} else if len(q.Statements) != 1 {
t.Fatalf("unexpected statement count: %d", len(q.Statements))
}
}
// Ensure the parser can parse a multi-statement query.
func Test_Parse_General_Multi(t *testing.T) {
s := `SELECT a FROM b; SELECT c FROM d`
q, err := sql.NewParser(strings.NewReader(s)).Parse()
if err != nil {
t.Fatalf("unexpected error: %s", err)
} else if len(q.Statements) != 2 {
t.Fatalf("unexpected statement count: %d", len(q.Statements))
}
}
func Test_Parse_Queries_Malformed(t *testing.T) {
var tests = []tester{
{
sql: "SELECT ` FROM person",
err: "found ` FROM person` but expected `field name`",
},
{
sql: `SELECT ' FROM person`,
err: "found ` FROM person` but expected `field name`",
},
{
sql: `SELECT " FROM person`,
err: "found ` FROM person` but expected `field name`",
},
{
sql: `SELECT "\" FROM person`,
err: "found `\" FROM person` but expected `field name`",
},
{
sql: `SELECT "\q" FROM person`,
err: "found `` but expected `field name`",
},
}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Select(t *testing.T) {
var tests = []tester{
{
sql: `!`,
err: "found `!` but expected `SELECT, INSERT, UPSERT, UPDATE, MODIFY, DELETE, RELATE, RECORD, DEFINE, RESYNC, REMOVE`",
},
{
sql: `SELECT`,
err: "found `` but expected `field name`",
},
{
sql: `SELECT FROM`,
err: "found `FROM` but expected `field name`",
},
{
sql: `SELECT *`,
err: "found `` but expected `FROM`",
},
{
sql: `SELECT * FROM`,
err: "found `` but expected `table name`",
},
{
sql: `SELECT * FROM per!son`,
err: "found `!` but expected `EOF, ;`",
},
{
sql: `SELECT * FROM person;`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Table{Name: "person"}},
}}},
},
{
sql: `SELECT ALL FROM person;`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Table{Name: "person"}},
}}},
},
{
sql: `SELECT * FROM person;;;`,
err: "found `;` but expected `SELECT, INSERT, UPSERT, UPDATE, MODIFY, DELETE, RELATE, RECORD, DEFINE, RESYNC, REMOVE`",
},
{
sql: `SELECT * FROM @`,
err: "found `` but expected `table name`",
},
{
sql: `SELECT * FROM person:uuid`,
err: "found `:` but expected `EOF, ;`",
},
{
sql: `SELECT * FROM @person`,
err: "found `` but expected `:`",
},
{
sql: `SELECT * FROM @person:`,
err: "found `` but expected `table id`",
},
{
sql: `SELECT * FROM person`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Table{Name: "person"}},
}}},
},
{
sql: `SELECT * FROM person, tweet`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Table{Name: "person"}, &sql.Table{Name: "tweet"}},
}}},
},
{
sql: `SELECT * FROM @person:123456`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Thing{Table: "person", ID: "123456"}},
}}},
},
{
sql: `SELECT * FROM @person:123.456`,
err: "found `123.456` but expected `table id`",
},
{
sql: `SELECT * FROM @person:123.456.789.012`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Thing{Table: "person", ID: "123.456.789.012"}},
}}},
},
{
skip: true,
sql: `SELECT * FROM @person:A250C5A3-948F-4657-88AD-FF5F27B5B24E`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Thing{Table: "person", ID: "A250C5A3-948F-4657-88AD-FF5F27B5B24E"}},
}}},
},
{
sql: `SELECT * FROM @person:8250C5A3-948F-4657-88AD-FF5F27B5B24E`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Thing{Table: "person", ID: "8250C5A3-948F-4657-88AD-FF5F27B5B24E"}},
}}},
},
{
sql: `SELECT * FROM @person:{Tobie Morgan Hitchcock}`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Thing{Table: "person", ID: "Tobie Morgan Hitchcock"}},
}}},
},
{
sql: `SELECT * FROM @{email addresses}:{tobie@abcum.com}`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Thing{Table: "email addresses", ID: "tobie@abcum.com"}},
}}},
},
{
sql: `SELECT * FROM @{email addresses}:{tobie+spam@abcum.com}`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Thing{Table: "email addresses", ID: "tobie+spam@abcum.com"}},
}}},
},
{
sql: `SELECT * FROM @{email addresses}:{this\nis\nodd}`,
err: "found `this\nis\nodd` but expected `table id`",
},
{
sql: `SELECT * FROM @{email addresses}:{this\qis\nodd}`,
err: "found `is` but expected `EOF, ;`",
},
{
sql: `SELECT *, temp AS test FROM person`,
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{
{Expr: &sql.Wildcard{}},
{Expr: &sql.IdentLiteral{Val: "temp"}, Alias: &sql.IdentLiteral{Val: "test"}},
},
Thing: []sql.Expr{&sql.Table{Name: "person"}},
}}},
},
{
sql: "SELECT `email addresses` AS emails FROM person",
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{
{Expr: &sql.IdentLiteral{Val: "email addresses"}, Alias: &sql.IdentLiteral{Val: "emails"}},
},
Thing: []sql.Expr{&sql.Table{Name: "person"}},
}}},
},
{
sql: "SELECT emails AS `email addresses` FROM person",
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{
{Expr: &sql.IdentLiteral{Val: "emails"}, Alias: &sql.IdentLiteral{Val: "email addresses"}},
},
Thing: []sql.Expr{&sql.Table{Name: "person"}},
}}},
},
{
sql: "SELECT * FROM person WHERE",
err: "found `` but expected `field name`",
},
{
sql: "SELECT * FROM person WHERE id",
err: "found `` but expected `IN, =, !=, >, <, >=, <=, =~, !~`",
},
{
sql: "SELECT * FROM person WHERE id =",
err: "found `` but expected `field value`",
},
{
sql: "SELECT * FROM person WHERE id = 1",
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Table{Name: "person"}},
Where: []sql.Expr{&sql.BinaryExpression{LHS: &sql.IdentLiteral{Val: "id"}, Op: "=", RHS: &sql.NumberLiteral{Val: 1}}},
}}},
},
{
sql: "SELECT * FROM person WHERE id != 1 AND id > 14 AND id < 31 AND id >= 15 AND id <= 30",
res: &sql.Query{Statements: []sql.Statement{&sql.SelectStatement{
Fields: []*sql.Field{{Expr: &sql.Wildcard{}}},
Thing: []sql.Expr{&sql.Table{Name: "person"}},
Where: []sql.Expr{
&sql.BinaryExpression{LHS: &sql.IdentLiteral{Val: "id"}, Op: "!=", RHS: &sql.NumberLiteral{Val: 1}},
&sql.BinaryExpression{LHS: &sql.IdentLiteral{Val: "id"}, Op: ">", RHS: &sql.NumberLiteral{Val: 14}},
&sql.BinaryExpression{LHS: &sql.IdentLiteral{Val: "id"}, Op: "<", RHS: &sql.NumberLiteral{Val: 31}},
&sql.BinaryExpression{LHS: &sql.IdentLiteral{Val: "id"}, Op: ">=", RHS: &sql.NumberLiteral{Val: 15}},
&sql.BinaryExpression{LHS: &sql.IdentLiteral{Val: "id"}, Op: "<=", RHS: &sql.NumberLiteral{Val: 30}},
},
}}},
},
{
skip: true,
sql: `SELECT ALL,
1a,
12 AS int,
13.90831 AS mean,
{some thing} AS something,
"some string" AS string
FROM
@person:a1,
@person:1a,
@person:{Tobie Morgan Hitchcock},
@{some table}:{Tobie Morgan Hitchcock},
@{email addresses}:{tobie+spam@abcum.com}
WHERE
id=true
OR 30 > test
OR firstname = "Tobie"
OR firstname = lastname
OR "London" IN tags
OR account IN ["@account:abcum","@account:gibboo","@account:acreon"];`,
},
}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Create(t *testing.T) {
var tests = []tester{
{
sql: `CREATE`,
err: "found `` but expected `table name`",
},
{
sql: `CREATE INTO`,
err: "found `` but expected `table name`",
},
{
sql: `CREATE person`,
err: "found `` but expected `SET`",
},
{
sql: `CREATE person SET firstname`,
err: "found `` but expected `=, +=, -=`",
},
{
sql: `CREATE person SET firstname = "Tobie"`,
res: &sql.Query{Statements: []sql.Statement{&sql.CreateStatement{
What: &sql.Table{Name: "person"},
Data: []sql.Expr{
&sql.BinaryExpression{LHS: &sql.IdentLiteral{Val: "firstname"}, Op: "=", RHS: &sql.StringLiteral{Val: "Tobie"}},
},
}}},
},
{
sql: `CREATE person SET firstname = "Tobie" something`,
err: "found `something` but expected `EOF, ;`",
},
}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Insert(t *testing.T) {
var tests = []tester{
{
sql: `INSERT`,
err: "found `` but expected `table name`",
},
{
sql: `INSERT INTO`,
err: "found `` but expected `table name`",
},
{
sql: `INSERT INTO person`,
err: "found `` but expected `SET`",
},
{
sql: `INSERT INTO person SET firstname`,
err: "found `` but expected `=, +=, -=`",
},
{
sql: `INSERT INTO person SET firstname = "Tobie"`,
res: &sql.Query{Statements: []sql.Statement{&sql.InsertStatement{
What: &sql.Table{Name: "person"},
Data: []sql.Expr{
&sql.BinaryExpression{LHS: &sql.IdentLiteral{Val: "firstname"}, Op: "=", RHS: &sql.StringLiteral{Val: "Tobie"}},
},
}}},
},
{
sql: `INSERT INTO person SET firstname = "Tobie" something`,
err: "found `something` but expected `EOF, ;`",
},
}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Upsert(t *testing.T) {
var tests = []tester{}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Update(t *testing.T) {
var tests = []tester{}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Delete(t *testing.T) {
var tests = []tester{}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Relate(t *testing.T) {
var tests = []tester{}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Record(t *testing.T) {
var tests = []tester{}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Define(t *testing.T) {
var tests = []tester{
{
sql: `DEFINE`,
err: "found `` but expected `INDEX, VIEW`",
},
// VIEW
{
sql: `DEFINE VIEW`,
err: "found `` but expected `name`",
},
{
sql: `DEFINE VIEW temp`,
err: "found `` but expected `MAP`",
},
{
sql: `DEFINE VIEW temp MAP`,
err: "found `` but expected `string`",
},
{
sql: "DEFINE VIEW temp MAP ``",
err: "found `` but expected `REDUCE`",
},
{
sql: "DEFINE VIEW temp MAP `` REDUCE",
err: "found `` but expected `string`",
},
{
sql: "DEFINE VIEW temp MAP `` REDUCE ``",
res: &sql.Query{Statements: []sql.Statement{&sql.DefineViewStatement{
View: &sql.IdentLiteral{Val: "temp"},
Map: &sql.StringLiteral{Val: ""},
Reduce: &sql.StringLiteral{Val: ""},
}}},
},
{
sql: "DEFINE VIEW temp MAP `\nemit()\n` REDUCE `\nreturn sum()\n`",
res: &sql.Query{Statements: []sql.Statement{&sql.DefineViewStatement{
View: &sql.IdentLiteral{Val: "temp"},
Map: &sql.StringLiteral{Val: "\nemit()\n"},
Reduce: &sql.StringLiteral{Val: "\nreturn sum()\n"},
}}},
},
{
sql: `DEFINE VIEW temp
MAP "
if (meta.table == 'person') {
if (doc.firstname && doc.lastname) {
emit([doc.lastname, doc.firstname, meta.id], null)
}
}
"
REDUCE "
return sum()
"`,
res: &sql.Query{Statements: []sql.Statement{&sql.DefineViewStatement{
View: &sql.IdentLiteral{Val: "temp"},
Map: &sql.StringLiteral{Val: "\nif (meta.table == 'person') {\n if (doc.firstname && doc.lastname) {\n emit([doc.lastname, doc.firstname, meta.id], null)\n }\n}\n"},
Reduce: &sql.StringLiteral{Val: "\nreturn sum()\n"},
}}},
},
{
sql: "DEFINE VIEW temp MAP `` REDUCE `` something",
err: "found `something` but expected `EOF, ;`",
},
// INDEX
{
sql: `DEFINE INDEX`,
err: "found `` but expected `name`",
},
{
sql: `DEFINE INDEX temp`,
err: "found `` but expected `ON`",
},
{
sql: `DEFINE INDEX temp ON`,
err: "found `` but expected `table name`",
},
{
sql: `DEFINE INDEX temp ON person`,
err: "found `` but expected `COLUMNS`",
},
{
sql: `DEFINE INDEX temp ON person COLUMNS`,
err: "found `` but expected `field name`",
},
{
sql: `DEFINE INDEX temp ON person COLUMNS firstname, lastname`,
res: &sql.Query{Statements: []sql.Statement{&sql.DefineIndexStatement{
Index: &sql.IdentLiteral{Val: "temp"},
Table: &sql.Table{Name: "person"},
Fields: []*sql.Field{
{Expr: &sql.IdentLiteral{Val: "firstname"}},
{Expr: &sql.IdentLiteral{Val: "lastname"}},
},
Unique: false,
}}},
},
{
sql: `DEFINE INDEX temp ON person COLUMNS firstname, lastname UNIQUE`,
res: &sql.Query{Statements: []sql.Statement{&sql.DefineIndexStatement{
Index: &sql.IdentLiteral{Val: "temp"},
Table: &sql.Table{Name: "person"},
Fields: []*sql.Field{
{Expr: &sql.IdentLiteral{Val: "firstname"}},
{Expr: &sql.IdentLiteral{Val: "lastname"}},
},
Unique: true,
}}},
},
{
sql: `DEFINE INDEX temp ON person COLUMNS firstname, lastname something UNIQUE`,
err: "found `something` but expected `EOF, ;`",
},
{
sql: `DEFINE INDEX temp ON person COLUMNS firstname, lastname UNIQUE something`,
err: "found `something` but expected `EOF, ;`",
},
}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Resync(t *testing.T) {
var tests = []tester{
{
sql: `RESYNC`,
err: "found `` but expected `INDEX, VIEW`",
},
// VIEW
{
sql: `RESYNC VIEW`,
err: "found `` but expected `name`",
},
{
sql: `RESYNC VIEW temp`,
res: &sql.Query{Statements: []sql.Statement{&sql.ResyncViewStatement{
View: &sql.IdentLiteral{Val: "temp"},
}}},
},
{
sql: `RESYNC VIEW temp something`,
err: "found `something` but expected `EOF, ;`",
},
// INDEX
{
sql: `RESYNC INDEX`,
err: "found `` but expected `name`",
},
{
sql: `RESYNC INDEX temp`,
err: "found `` but expected `ON`",
},
{
sql: `RESYNC INDEX temp ON`,
err: "found `` but expected `table name`",
},
{
sql: `RESYNC INDEX temp ON person`,
res: &sql.Query{Statements: []sql.Statement{&sql.ResyncIndexStatement{
Index: &sql.IdentLiteral{Val: "temp"},
Table: &sql.Table{Name: "person"},
}}},
},
{
sql: `RESYNC INDEX temp ON person something`,
err: "found `something` but expected `EOF, ;`",
},
}
for _, test := range tests {
testsql(t, test)
}
}
func Test_Parse_Queries_Remove(t *testing.T) {
var tests = []tester{
{
sql: `REMOVE`,
err: "found `` but expected `INDEX, VIEW`",
},
// VIEW
{
sql: `REMOVE VIEW`,
err: "found `` but expected `name`",
},
{
sql: `REMOVE VIEW temp`,
res: &sql.Query{Statements: []sql.Statement{&sql.RemoveViewStatement{
View: &sql.IdentLiteral{Val: "temp"},
}}},
},
{
sql: `REMOVE VIEW temp something`,
err: "found `something` but expected `EOF, ;`",
},
// INDEX
{
sql: `REMOVE INDEX`,
err: "found `` but expected `name`",
},
{
sql: `REMOVE INDEX temp`,
err: "found `` but expected `ON`",
},
{
sql: `REMOVE INDEX temp ON`,
err: "found `` but expected `table name`",
},
{
sql: `REMOVE INDEX temp ON person`,
res: &sql.Query{Statements: []sql.Statement{&sql.RemoveIndexStatement{
Index: &sql.IdentLiteral{Val: "temp"},
Table: &sql.Table{Name: "person"},
}}},
},
{
sql: `RESYNC INDEX temp ON person something`,
err: "found `something` but expected `EOF, ;`",
},
}
for _, test := range tests {
testsql(t, test)
}
}

101
sql/things.go Normal file
View file

@ -0,0 +1,101 @@
// 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) parseThings() ([]Expr, error) {
var ts []Expr
for {
t, err := p.parseThing()
if err != nil {
return nil, err
}
ts = append(ts, t)
// If the next token is not a comma then break the loop.
if tok, _ := p.scanIgnoreWhitespace(); tok != COMMA {
p.unscan()
break
}
}
return ts, nil
}
func (p *Parser) parseThing() (Expr, error) {
var (
err error
tok Token
lit string
)
_, _, exi := p.mightBe(EAT)
if exi == false {
t := &Table{}
// Parse table name
tok, lit = p.scan()
if !is(tok, IDENT, NUMBER) {
p.unscan()
return nil, &ParseError{Found: lit, Expected: []string{"table name"}}
}
t.Name = lit
return t, nil
}
if exi == true {
t := &Thing{}
// Parse table name
tok, lit = p.scan()
if !is(tok, IDENT, NUMBER) {
p.unscan()
return nil, &ParseError{Found: lit, Expected: []string{"table name"}}
}
t.Table = lit
// Next token should be :
_, _, err = p.shouldBe(COLON)
if err != nil {
return nil, err
}
// Parse table id
tok, lit = p.scan()
if !is(tok, IDENT, NUMBER) {
p.unscan()
return nil, &ParseError{Found: lit, Expected: []string{"table id"}}
}
t.ID = lit
return t, nil
}
return nil, nil
}

287
sql/token.go Normal file
View file

@ -0,0 +1,287 @@
// 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
// Token defines a lexical token
type Token int
const (
// special
ILLEGAL Token = iota
EOF
WS
// literals
literalsBeg
DATE // 1970-01-01
TIME // 1970-01-01T00:00:00+00:00
NANO // 1970-01-01T00:00:00.000000000+00:00
PATH // :friend
JSON // {"test":true}
IDENT // something
STRING // "something"
REGION // "a multiline \n string"
NUMBER // 123456
DOUBLE // 123.456
REGEX // /.*/
DURATION // 13h
EAT // @
DOT // .
COMMA // ,
LPAREN // (
RPAREN // )
LBRACK // [
RBRACK // ]
COLON // :
SEMICOLON // ;
literalsEnd
// operators
operatorBeg
ADD // +
SUB // -
MUL // *
DIV // /
INC // +=
DEC // -=
EQ // =
NEQ // !=
LT // <
LTE // <=
GT // >
GTE // >=
EQR // =~
NER // !~
operatorEnd
// literals
keywordsBeg
ALL
AND
AS
ASC
AT
BY
COLUMNS
CREATE
DEFINE
DELETE
DESC
DISTINCT
EMPTY
FALSE
FROM
GROUP
IN
INDEX
INSERT
INTO
LIMIT
MAP
MODIFY
NULL
OFFSET
ON
OR
ORDER
RECORD
REDUCE
RELATE
REMOVE
RESYNC
SELECT
SET
START
TO
TRUE
UNIQUE
UPDATE
UPSERT
VERSION
VIEW
WHERE
keywordsEnd
)
var tokens = [...]string{
ILLEGAL: "ILLEGAL",
EOF: "EOF",
WS: "WS",
// literals
DATE: "DATE",
TIME: "TIME",
NANO: "NANO",
PATH: "PATH",
JSON: "JSON",
IDENT: "IDENT",
STRING: "STRING",
REGION: "REGION",
NUMBER: "NUMBER",
DOUBLE: "DOUBLE",
REGEX: "REGEX",
DURATION: "DURATION",
EAT: "@",
DOT: ".",
COMMA: ",",
LPAREN: "(",
RPAREN: ")",
LBRACK: "[",
RBRACK: "]",
COLON: ":",
SEMICOLON: ";",
// operators
ADD: "+",
SUB: "-",
MUL: "*",
DIV: "/",
INC: "+=",
DEC: "-=",
EQ: "=",
NEQ: "!=",
LT: "<",
LTE: "<=",
GT: ">",
GTE: ">=",
EQR: "=~",
NER: "!~",
// keywords
ALL: "ALL",
AND: "AND",
AS: "AS",
ASC: "ASC",
AT: "AT",
BY: "BY",
COLUMNS: "COLUMNS",
CREATE: "CREATE",
DEFINE: "DEFINE",
DELETE: "DELETE",
DESC: "DESC",
DISTINCT: "DISTINCT",
EMPTY: "EMPTY",
FALSE: "FALSE",
FROM: "FROM",
GROUP: "GROUP",
IN: "IN",
INDEX: "INDEX",
INSERT: "INSERT",
INTO: "INTO",
LIMIT: "LIMIT",
MAP: "MAP",
MODIFY: "MODIFY",
NULL: "NULL",
ON: "ON",
OR: "OR",
ORDER: "ORDER",
RECORD: "RECORD",
REDUCE: "REDUCE",
RELATE: "RELATE",
REMOVE: "REMOVE",
RESYNC: "RESYNC",
SELECT: "SELECT",
SET: "SET",
START: "START",
TO: "TO",
TRUE: "TRUE",
UNIQUE: "UNIQUE",
UPDATE: "UPDATE",
UPSERT: "UPSERT",
VIEW: "VIEW",
VERSION: "VERSION",
WHERE: "WHERE",
}
var literals map[string]Token
var operator map[string]Token
var keywords map[string]Token
func init() {
literals = make(map[string]Token)
for tok := literalsBeg + 1; tok < literalsEnd; tok++ {
literals[tokens[tok]] = tok
}
operator = make(map[string]Token)
for tok := operatorBeg + 1; tok < operatorEnd; tok++ {
operator[tokens[tok]] = tok
}
keywords = make(map[string]Token)
for tok := keywordsBeg + 1; tok < keywordsEnd; tok++ {
keywords[tokens[tok]] = tok
}
}
func lookup(lookups []Token) (literals []string) {
for _, token := range lookups {
literals = append(literals, token.String())
}
return
}
func (tok Token) precedence() int {
switch tok {
case OR:
return 1
case AND:
return 2
case EQ, NEQ, EQR, NER, LT, LTE, GT, GTE:
return 3
case ADD, SUB:
return 4
case MUL, DIV:
return 5
}
return 0
}
func (tok Token) String() string {
if tok >= 0 && tok < Token(len(tokens)) {
return tokens[tok]
}
return ""
}
func (tok Token) isLiteral() bool { return tok > literalsBeg && tok < literalsEnd }
func (tok Token) isKeyword() bool { return tok > keywordsBeg && tok < keywordsEnd }
func (tok Token) isOperator() bool { return tok > operatorBeg && tok < operatorEnd }

19
sql/update.go Normal file
View file

@ -0,0 +1,19 @@
// 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) parseUpdateStatement() (Statement, error) {
return nil, nil
}

48
sql/upsert.go Normal file
View file

@ -0,0 +1,48 @@
// 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) parseUpsertStatement() (*UpsertStatement, error) {
stmt := &UpsertStatement{}
var err error
// Next token might be INTO
_, _, _ = p.mightBe(INTO)
// Parse table name
if stmt.What, err = p.parseThing(); err != nil {
return nil, err
}
// Next token should be SET
if _, _, err = p.shouldBe(SET); err != nil {
return nil, err
}
// Parse data set
if stmt.Data, err = p.parseSet(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}

111
sql/util.go Normal file
View file

@ -0,0 +1,111 @@
// 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
import (
"encoding/json"
"strconv"
"time"
)
func in(token Token, tokens []Token) bool {
for _, t := range tokens {
if token == t {
return true
}
}
return false
}
func is(token Token, tokens ...Token) bool {
for _, t := range tokens {
if token == t {
return true
}
}
return false
}
func declare(tok Token, lit string) Expr {
switch tok {
case NULL:
return &Null{}
case ALL:
return &Wildcard{}
case ASC:
return &DirectionLiteral{Val: true}
case DESC:
return &DirectionLiteral{Val: false}
case IDENT:
return &IdentLiteral{Val: lit}
case DATE:
t, _ := time.Parse("2006-01-02", lit)
return &DatetimeLiteral{Val: t}
case TIME:
t, _ := time.Parse(time.RFC3339, lit)
return &DatetimeLiteral{Val: t}
case NANO:
t, _ := time.Parse(time.RFC3339Nano, lit)
return &DatetimeLiteral{Val: t}
case TRUE:
return &BooleanLiteral{Val: true}
case FALSE:
return &BooleanLiteral{Val: false}
case STRING:
return &StringLiteral{Val: lit}
case REGION:
return &StringLiteral{Val: lit}
case NUMBER:
i, _ := strconv.ParseInt(lit, 10, 64)
return &NumberLiteral{Val: i}
case DOUBLE:
f, _ := strconv.ParseFloat(lit, 64)
return &DoubleLiteral{Val: f}
case DURATION:
return &DurationLiteral{Val: 0}
case JSON:
var j interface{}
b := []byte(lit)
json.Unmarshal(b, &j)
return &JSONLiteral{Val: j}
}
return lit
}

95
sql/view.go Normal file
View file

@ -0,0 +1,95 @@
// 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) parseDefineViewStatement() (*DefineViewStatement, error) {
stmt := &DefineViewStatement{}
var err error
// Parse index name
if stmt.View, err = p.parseIdent(); err != nil {
return nil, err
}
// Next token should be COLUMNS
if _, _, err = p.shouldBe(MAP); err != nil {
return nil, err
}
// Parse columns
if stmt.Map, err = p.parseRegion(); err != nil {
return nil, err
}
// Next token should be COLUMNS
if _, _, err = p.shouldBe(REDUCE); err != nil {
return nil, err
}
// Parse columns
if stmt.Reduce, err = p.parseRegion(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}
func (p *Parser) parseResyncViewStatement() (*ResyncViewStatement, error) {
stmt := &ResyncViewStatement{}
var err error
// Parse index name
if stmt.View, err = p.parseIdent(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}
func (p *Parser) parseRemoveViewStatement() (*RemoveViewStatement, error) {
stmt := &RemoveViewStatement{}
var err error
// Parse index name
if stmt.View, err = p.parseIdent(); err != nil {
return nil, err
}
// Next token should be EOF
if _, _, err = p.shouldBe(EOF, SEMICOLON); err != nil {
return nil, err
}
return stmt, nil
}