Add initial sql parser code
This commit is contained in:
parent
7501d7c78b
commit
89b71cbe72
25 changed files with 3166 additions and 0 deletions
267
sql/ast.go
Normal file
267
sql/ast.go
Normal 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
48
sql/create.go
Normal 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
31
sql/define.go
Normal 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
19
sql/delete.go
Normal 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
15
sql/doc.go
Normal 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
31
sql/error.go
Normal 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
139
sql/exprs.go
Normal 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
118
sql/index.go
Normal 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
48
sql/insert.go
Normal 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
19
sql/modify.go
Normal 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
183
sql/parser.go
Normal 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
19
sql/record.go
Normal 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
19
sql/relate.go
Normal 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
31
sql/remove.go
Normal 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
31
sql/resync.go
Normal 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
405
sql/scanner.go
Normal 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
278
sql/select.go
Normal 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
59
sql/set.go
Normal 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
745
sql/sql_test.go
Normal 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
101
sql/things.go
Normal 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
287
sql/token.go
Normal 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
19
sql/update.go
Normal 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
48
sql/upsert.go
Normal 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
111
sql/util.go
Normal 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
95
sql/view.go
Normal 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
|
||||
|
||||
}
|
Loading…
Reference in a new issue