mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
* squashed 45 commits into one :) * tlt/sql experiment (#2035) * Move PlanOperator to sql3/planner/types package includes: type PlanOperatorColumn struct type PlanOperator interface * Remove planner dependencies from pilosa package The goal after this is to prevent the planner package (which doesn't exist yet) from being imported by the pilosa package; we just want it injected into the server in server/server.go. This is because the planner package uses pilosa types, so we need to avoid circular dependencies. Added ExecutionPlannerFn Make public: pilosa.ExecOptions Added a pilosa.Executor interface Added a planner.types.CompilePlanner interface Isolated the planner calls to: - Executor.Execute() - *API.[method]() * Move executionplanner files into the sql3/planner package. This required a bit of gymnastics, and there are some things around FieldOptions which need to be addressed soon. * Remove the hacky FieldOptions stuff I added earlier This implementation just uses the pilosa.FieldOption functional options provided by the API (as opposed to trying to build a FieldOptions object. It also changes field types to constants. These are private for now, but if we need to make them public, we should put them in the planner/types package. * Implement the "scale" value from Decimal(scale) Also, precision and scale were currently reversed in the parser. This fixes that. * Modify the parser to handle CACHETYPE <type> SIZE <size> It's a little odd to me that the cache type values are Tokens, but I guess it's ok. One thing to keep in mind is that FeatureBase expects lowercase values, so this commit changes the parser to set the value to the lowercase version of the type. * Fix the /sql2 tests This entailed a combination of commenting out or t.Skip()-ing tests which covered code in the parser that has been commented out or removed as not currently supported in sql3. It also adds some coverage for the sql.Contraint stringers. * Prevent JSON sql results from containing closing commas This commit just re-works the existing output code to avoid inserting closing commas (which results in invalid JSON). * Enhance the CREATE TABLE test coverage. In particular, ensure that the fields which get created in FeatureBase are what we expect based on the fields defined in the CREATE TABLE statement. This also ensures that the TIMEQUANTUM and CACHETYPE contraints are not provided for the same field (since those constraints are not supported together). * Adjust the EBNF file to indicate SIZE contraint is optional A CACHETYPE can be provided without a SIZE. This change indicates that SIZE is optional. * Remove `executionplanner_` from file names (#2040) * implementation of ALTER TABLE (sans column RENAME) * refactored expression analysis; added more robust type checking; all unary and bin ops function on ints * added type support for expressions; full bin/unary op support; added cast; more literal support * cast int to all other types * all literals (except idset, stringset & timestamp) make it thru; cast to all types with int as source now works * implemented LIKE/NOT LIKE * Implemented IS [NOT] NULL * Move sql2 files into sql3/parser package (#2045) * Move sql2 files into sql3/parser package This also removes the sql2 package. * Fix tests which were typing _id fields as INT intead of ID * implemented BETWEEN, NOT BETWEEN * Add featurebase/error package (#2046) * Add featurebase/error package I copied the `dax/errors` package which I am starting to use in the DAX prototype into `featurebase/errors` in order to start using it with the sql3 package. It's basically a wrapper around `github.com/pkg/errors`, but it uses a customer coded error. The sql package can define its own errors based on the `featurebase/errors` types. Then do things like `Wrap()` and `Is()`. * Address the linter complaints: shadowed variables, unreachable code * implemented IN & NOT IN with expression lists * first cut of CASE * Fixed some errors from rebase * updated bnf; removed unused code; tightened up error handling * first crack at basic CLI for SQL3 Use: `featurebase cli` Still lots to do here, but for example: > select count(*) from tremor +--------------+ | COUNT | +--------------+ | 1.158321e+06 | +--------------+ * Iterate on the CLI (#2057) Handle the errors. Add an "exit" command. Add some general formatting and white space. Add termination character: ";" (semicolon) This commit allows a user to provide multiple or partial SQL statements. Example of multiple statements: ``` show tables; select * from foo; ``` Example of partial (multi-line) statements: ``` select * from foo; ``` Don't uppercase the header values * error refactoring; first cut of TOP; remove unused code; use log.Printf instead of fmt.Printf * fixed a bug with QualifiedRef from refactoring; added bones of INSERT; removal of unused code; tightened up errors more; fixed failing tests * single value list for INSERT * Update bnf per discussion with Travis; INSERT now doing the requisite stuff * Pat's eyes went square - nothing wrong with TOP, Pat needed to learn arrays again. * improved some errors; fixed tests to suit * send warnings back in the api; update CLI to display warnings * start warning on stuff not implemented so we don't get bugged about it * Tlt/sql experiment (#2063) * Expresssion -> Expression * Add SQL planner test - adds a test to which it is easier to add tables and SQL statments - un-exports all of the expression types - removes the planner pointer from the expression types (it can be added back later if need be) * Fix where clause on a string field Prior to this commit, the binary expression for a where clause on a string field was building the call by providing a range operator which is typically used for BSI fields. This changes it to use the call.Args for string values. * Update planner tests to handle multiple sql for the same results * Reorganize SQL tests Introduce a test/helpers package and move shared MustQueryRows into that package. * Add a compatibility map for field types. (#2064) This is primarily to address the fact that ID fields were previously incompatible with INT literals. We should probably consider introducing a custom type for FieldType which can be used to define compatibilities. * significantly refactored type checking * Handle nil (NULL) values in the sql CLI. (#2067) go-pretty panics if the interface{} field value is nil. This replaces nil values with a "NULL" string. * Squash some commits fixed a still failing test added line, col to all error messages refactored source handling to enable table aliases fixed some copypasta per review warnings for order by & topn; implemented select as a source starting to handle in (select...); added stub for optimizer JSON-encode the sql error and warning strings (#2069) Error strings with unencoded characters (like double quotes) were resulting in invalid json. got insert working; added symbol table; added concrete optimizer; added nascent NestedLoopsOperator; rewrite "where foo in (select..." as inner join * all about the sets (#2085) * implemented setcontains() * implemented set literal; insert set column values; setcontains/all/any both in expr eval and pql filters * Convert test to use latest framework. (#2086) * fixed some comments * removed refactored tests Co-authored-by: Travis Turner <travis@pilosa.com> * Add support for Decimal fields to the sql test. (#2090) * dates (#2094) * return dates as strings in output; tightened up decimal type checking * return dates as strings in output; tightened up decimal type checking * fixed failing tests after decimal changes * can now insert decimal values * implemented insert for timestamp data type; implemented current_date, current_timestamp constants * fixed some failing tests * handle date literals from strings in insert statements * changes from feedback * Fix pointer method error * sql3 API interface (#2110) * Introduce API-related interfaces: SchemaAPI, ComputeAPI The sql3 code was relying on the pointer: *pilosa.API in order to call API methods directly on the local node. If we want to import and use the sql3 package in another service (the DAX queryer, for example), we need to be able to use an implementation of an interface for those API method calls. This commit introduces two interfaces, both automatically implemented by pilosa.API: - SchemaAPI - ComputeAPI * Convert sql3 code to use IndexInfo instead of Index The sql3 code was relying on a *pilosa.Index and its methods to get general information like index and field name, type, etc. This commit converts everything to use a *pilosa.IndexInfo instead. This allows us to modify the SchemaAPI interface to also return IndexInfo instead of Index, which will be a lot easier to implement in a non-pilosa package (like DAX); creating a *pilosa.Index requires providing things like data directory paths and holders, which are not necessary for these use cases. * Unary and Binary Ops R US plus CAST (#2111) * implemented string literal for timestamp epoch * fixed failing test * fixed the failing test again * refactored tests; implemented unary op tests for all datatypes; implemented binop tests for int/int, int/id, int/decimal & ID/int * implemented all binary ops for INT & all other types, ID & all other types * implemented binary ops for DECIMAL types & all other types * added STRING & BOOL to various tests; implemented all remaining binOp tests * fix up some stuff after rebasing * refactored test defs into multiple files; implemented CAST for every datatype * added tests for like/not like * addressed review feedback * addressed type review feedback * tightened up IS [NOT] NULL behavior plus tests (#2118) * tightened up IS [NOT] NULL behavior plus tests * BETWEEN/NOT BETWEEN with all data types * addressed review feedback * Handle negative integers in column min/max constraints (#2120) This commit parses the min/max contraint as an expression, as opposed to an int literal, so that negative values are treated as Unary expressions. There currently isn't support for min/max constraints on `decimal` fiels, so for now this change only expects +/- integer values. * Implement the CREATE TABLE keypartitions logic (#2123) * Execution time, IN/NOT IN & multiple aggregates (#2124) * added display of execution time * IN/NOT IN tests for all data types * fixed date parsing * removed duplicative tests * refactoring aggregates * suport multiple aggregates * Address review feedback * final round of feedback * Add method SchemaAPI.CreateIndexAndFields() (#2127) In order to support a CREATE TABLE statement as a single command, this commit alters the SchemaAPI interface to contain a single method which handles both the index and its fields. It also updates the sql3 code to use this interface instead of CreateIndex() and CreateField() indepedently. * Symbol Handling (Again) (#2129) * Refactored symbol handling in the planner; re-instated the select as source tests * removed commented out code * addressing review feedback * Move hard-coded _id field out of planner and into interface implementation (#2130) This commit moves the hard-coded addition of the `_id` field from the planner to the SchemaAPI.IndexInfo() implementation method. NOTE: If anything was expecting SchemaAPI.Schema() to also return the `_id` field as part of its field list in each table, then it would not be there because the `_id` field is only added in the IndexInfo() method for now. Currently that's not a problem because nothing is expecting the `_id` field for `Schema()`. * Multiple aggregates, all aggregates stand alone and in GROUP BY (#2132) * handle multiple aggregates in group by queries * added handling for avg() aggregate both stand alone and in group by * tightened up sum & avg outside of group by * added min, max & percentile * added warnings * Make MaterializedRowSet implement the PlanOperator interface. (#2133) This commit refactors the PQLMultiGroupByOperator to have a PlanOperator as its output. Then, when it initializes, it sets up a MaterializedRowSet and populates that with the values from the multiple group by operations. * added explicit min/max pql operators * saved a file I forgot to save * per review * Un-indent some if/else nesting (#2136) Co-authored-by: Travis Turner <travis@pilosa.com> * Add optional `name` argument to test structs. This commit adds the `name` argument to `tableTest` and `sqlTest` so that a test can be optionally named. This allows a developer to more easily run/identify a particular test by name. * Inbuilt functions (redux) (#2141) * set functions type parameter type checking * implemented datepart * include SQL3 type in SHOW COLUMNS output * fixed select as source; failing SHOW COLUMNS test * select in select list * dump output columns; handle optimization for select list subqueries * make it an error to return multiple rows for a select list subquery * added description * contants and test coverage for datepart function * SQL3 Refactor-palooza (#2182) * removed unneeded IsAggregate() * first cut of working nested loops operator aka INNER JOIN * remove selectListItemPlanExpression * added some warnings * all the tests are passing again! * addressed some linter complaints * added basic order by * bug fixes; added 'or replace'/'replace' to insert * for insert references should return appropriately * added back ability to use subquery singleton expressions * removed dead code; fixed test * json-able plan, Schema() plus refactoring * fixed dumb code * add some tests for time quantum behavior * Code cleanup during review. Also fixed INSERT to keyed table bug. This commit contains a lot of minor adjustments made during code review. It also contains a bug fix that was preventing INSERT into a keyed table (i.e. _id type STRING) from working. Co-authored-by: Travis Turner <travis@molecula.com> * Fix expected min/max on timestamp column test (decimal field) I don't know why this changed, but presumably something to do with decimal related work that happened on master. * Fix compile problem after rebase * review feedback Co-authored-by: Matthew Jaffee <jaffee@pilosa.com> Co-authored-by: Travis Turner <travis@pilosa.com> Co-authored-by: Travis Turner <travis@molecula.com> Co-authored-by: Fletcher Haynes <fletcher@capitalprawn.com>
3201 lines
72 KiB
Go
3201 lines
72 KiB
Go
// Copyright 2021 Molecula Corp. All rights reserved.
|
|
package parser
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Parser represents a SQL parser.
|
|
type Parser struct {
|
|
s *Scanner
|
|
|
|
pos Pos // current position
|
|
tok Token // current token
|
|
lit string // current literal value
|
|
full bool // buffer full
|
|
}
|
|
|
|
// NewParser returns a new instance of Parser that reads from r.
|
|
func NewParser(r io.Reader) *Parser {
|
|
return &Parser{
|
|
s: NewScanner(r),
|
|
}
|
|
}
|
|
|
|
// ParseExprString parses s into an expression. Returns nil if s is blank.
|
|
func ParseExprString(s string) (Expr, error) {
|
|
if s == "" {
|
|
return nil, nil
|
|
}
|
|
return NewParser(strings.NewReader(s)).ParseExpr()
|
|
}
|
|
|
|
// MustParseExprString parses s into an expression. Panic on error.
|
|
func MustParseExprString(s string) Expr {
|
|
expr, err := ParseExprString(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return expr
|
|
}
|
|
|
|
func (p *Parser) ParseStatement() (stmt Statement, err error) {
|
|
switch tok := p.peek(); tok {
|
|
case EOF:
|
|
return nil, io.EOF
|
|
//case EXPLAIN:
|
|
// if stmt, err = p.parseExplainStatement(); err != nil {
|
|
// return stmt, err
|
|
// }
|
|
default:
|
|
if stmt, err = p.parseNonExplainStatement(); err != nil {
|
|
return stmt, err
|
|
}
|
|
}
|
|
|
|
// Read trailing semicolon or end of file.
|
|
if tok := p.peek(); tok != EOF && tok != SEMI {
|
|
return stmt, p.errorExpected(p.pos, p.tok, "semicolon or EOF")
|
|
}
|
|
p.scan()
|
|
|
|
return stmt, nil
|
|
}
|
|
|
|
/*
|
|
// parseExplain parses EXPLAIN [QUERY PLAN] STMT.
|
|
func (p *Parser) parseExplainStatement() (_ *ExplainStatement, err error) {
|
|
var tok Token
|
|
|
|
// Parse initial "EXPLAIN" token.
|
|
var stmt ExplainStatement
|
|
stmt.Explain, tok, _ = p.scan()
|
|
assert(tok == EXPLAIN)
|
|
|
|
// Parse optional "QUERY PLAN" tokens.
|
|
if p.peek() == QUERY {
|
|
stmt.Query, _, _ = p.scan()
|
|
|
|
if p.peek() != PLAN {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "PLAN")
|
|
}
|
|
stmt.QueryPlan, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse statement to be explained.
|
|
if stmt.Stmt, err = p.parseNonExplainStatement(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
// parseStmt parses all statement types.
|
|
func (p *Parser) parseNonExplainStatement() (Statement, error) {
|
|
switch p.peek() {
|
|
//case ANALYZE:
|
|
// return p.parseAnalyzeStatement()
|
|
case ALTER:
|
|
return p.parseAlterTableStatement()
|
|
// case BEGIN:
|
|
// return p.parseBeginStatement()
|
|
// case COMMIT, END:
|
|
// return p.parseCommitStatement()
|
|
// case ROLLBACK:
|
|
// return p.parseRollbackStatement()
|
|
// case SAVEPOINT:
|
|
// return p.parseSavepointStatement()
|
|
// case RELEASE:
|
|
// return p.parseReleaseStatement()
|
|
case BULK:
|
|
return p.parseBulkInsertStatement()
|
|
case CREATE:
|
|
return p.parseCreateStatement()
|
|
case DROP:
|
|
return p.parseDropStatement()
|
|
case SELECT:
|
|
return p.parseSelectStatement(false, nil)
|
|
case INSERT, REPLACE:
|
|
return p.parseInsertStatement(nil)
|
|
case UPDATE:
|
|
return p.parseUpdateStatement(nil)
|
|
case DELETE:
|
|
return p.parseDeleteStatement(nil)
|
|
// case WITH:
|
|
// return p.parseWithStatement()
|
|
case SHOW:
|
|
return p.parseShowStatement()
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, "statement")
|
|
}
|
|
}
|
|
|
|
// parseWithStatement is called only from parseNonExplainStatement as we don't
|
|
// know what kind of statement we'll have after the CTEs (e.g. SELECT, INSERT, etc).
|
|
/*func (p *Parser) parseWithStatement() (Statement, error) {
|
|
withClause, err := p.parseWithClause()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch p.peek() {
|
|
case SELECT, VALUES:
|
|
return p.parseSelectStatement(false, withClause)
|
|
case INSERT, REPLACE:
|
|
return p.parseInsertStatement(withClause)
|
|
case UPDATE:
|
|
return p.parseUpdateStatement(withClause)
|
|
case DELETE:
|
|
return p.parseDeleteStatement(withClause)
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, "SELECT, VALUES, INSERT, REPLACE, UPDATE, or DELETE")
|
|
}
|
|
}*/
|
|
|
|
func (p *Parser) parseShowStatement() (Statement, error) {
|
|
assert(p.peek() == SHOW)
|
|
show, _, _ := p.scan()
|
|
|
|
switch p.peek() {
|
|
case TABLES:
|
|
return p.parseShowTablesStatement(show)
|
|
case COLUMNS:
|
|
return p.parseShowColumnsStatement(show)
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, "TABLES, COLUMNS")
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseShowTablesStatement(showPos Pos) (*ShowTablesStatement, error) {
|
|
switch p.peek() {
|
|
case TABLES:
|
|
var stmt ShowTablesStatement
|
|
stmt.Show = showPos
|
|
stmt.Tables, _, _ = p.scan()
|
|
return &stmt, nil
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, "TABLES")
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseShowColumnsStatement(showPos Pos) (_ *ShowColumnsStatement, err error) {
|
|
assert(p.peek() == COLUMNS)
|
|
columns, _, _ := p.scan()
|
|
|
|
var stmt ShowColumnsStatement
|
|
stmt.Show = showPos
|
|
stmt.Columns = columns
|
|
|
|
switch p.peek() {
|
|
case FROM:
|
|
stmt.From, _, _ = p.scan()
|
|
if stmt.TableName, err = p.parseIdent("table name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, "FROM")
|
|
}
|
|
}
|
|
|
|
/*func (p *Parser) parseBeginStatement() (*BeginStatement, error) {
|
|
assert(p.peek() == BEGIN)
|
|
|
|
var stmt BeginStatement
|
|
stmt.Begin, _, _ = p.scan()
|
|
|
|
// Parse transaction type.
|
|
switch p.peek() {
|
|
case DEFERRED:
|
|
stmt.Deferred, _, _ = p.scan()
|
|
case IMMEDIATE:
|
|
stmt.Immediate, _, _ = p.scan()
|
|
case EXCLUSIVE:
|
|
stmt.Exclusive, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse optional TRANSCTION keyword.
|
|
if p.peek() == TRANSACTION {
|
|
stmt.Transaction, _, _ = p.scan()
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseCommitStatement() (*CommitStatement, error) {
|
|
assert(p.peek() == COMMIT || p.peek() == END)
|
|
|
|
var stmt CommitStatement
|
|
if p.peek() == COMMIT {
|
|
stmt.Commit, _, _ = p.scan()
|
|
} else {
|
|
stmt.End, _, _ = p.scan()
|
|
}
|
|
|
|
if p.peek() == TRANSACTION {
|
|
stmt.Transaction, _, _ = p.scan()
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseRollbackStatement() (_ *RollbackStatement, err error) {
|
|
assert(p.peek() == ROLLBACK)
|
|
|
|
var stmt RollbackStatement
|
|
stmt.Rollback, _, _ = p.scan()
|
|
|
|
// Parse optional "TRANSACTION".
|
|
if p.peek() == TRANSACTION {
|
|
stmt.Transaction, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse optional "TO SAVEPOINT savepoint-name"
|
|
if p.peek() == TO {
|
|
stmt.To, _, _ = p.scan()
|
|
if p.peek() == SAVEPOINT {
|
|
stmt.Savepoint, _, _ = p.scan()
|
|
}
|
|
if stmt.SavepointName, err = p.parseIdent("savepoint name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseSavepointStatement() (_ *SavepointStatement, err error) {
|
|
assert(p.peek() == SAVEPOINT)
|
|
|
|
var stmt SavepointStatement
|
|
stmt.Savepoint, _, _ = p.scan()
|
|
if stmt.Name, err = p.parseIdent("savepoint name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseReleaseStatement() (_ *ReleaseStatement, err error) {
|
|
assert(p.peek() == RELEASE)
|
|
|
|
var stmt ReleaseStatement
|
|
stmt.Release, _, _ = p.scan()
|
|
|
|
if p.peek() == SAVEPOINT {
|
|
stmt.Savepoint, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("savepoint name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
func (p *Parser) parseCreateStatement() (Statement, error) {
|
|
assert(p.peek() == CREATE)
|
|
pos, tok, _ := p.scan()
|
|
|
|
switch p.peek() {
|
|
case TABLE:
|
|
return p.parseCreateTableStatement(pos)
|
|
/* case VIEW:
|
|
return p.parseCreateViewStatement(pos)
|
|
case INDEX, UNIQUE:
|
|
return p.parseCreateIndexStatement(pos)
|
|
case TRIGGER:
|
|
return p.parseCreateTriggerStatement(pos)*/
|
|
default:
|
|
return nil, p.errorExpected(pos, tok, "TABLE")
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseDropStatement() (Statement, error) {
|
|
assert(p.peek() == DROP)
|
|
pos, tok, _ := p.scan()
|
|
|
|
switch p.peek() {
|
|
case TABLE:
|
|
return p.parseDropTableStatement(pos)
|
|
/* case VIEW:
|
|
return p.parseDropViewStatement(pos)
|
|
case INDEX:
|
|
return p.parseDropIndexStatement(pos)
|
|
case TRIGGER:
|
|
return p.parseDropTriggerStatement(pos)*/
|
|
default:
|
|
return nil, p.errorExpected(pos, tok, "TABLE")
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseCreateTableStatement(createPos Pos) (_ *CreateTableStatement, err error) {
|
|
assert(p.peek() == TABLE)
|
|
|
|
var stmt CreateTableStatement
|
|
stmt.Create = createPos
|
|
stmt.Table, _, _ = p.scan()
|
|
|
|
// Parse optional "IF NOT EXISTS".
|
|
if p.peek() == IF {
|
|
stmt.If, _, _ = p.scan()
|
|
|
|
pos, tok, _ := p.scan()
|
|
if tok != NOT {
|
|
return &stmt, p.errorExpected(pos, tok, "NOT")
|
|
}
|
|
stmt.IfNot = pos
|
|
|
|
pos, tok, _ = p.scan()
|
|
if tok != EXISTS {
|
|
return &stmt, p.errorExpected(pos, tok, "EXISTS")
|
|
}
|
|
stmt.IfNotExists = pos
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("table name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
// Parse either a column/constraint list or build table from "AS <select>".
|
|
switch p.peek() {
|
|
case LP:
|
|
stmt.Lparen, _, _ = p.scan()
|
|
|
|
if stmt.Columns, err = p.parseColumnDefinitions(); err != nil {
|
|
return &stmt, err
|
|
} /*else if stmt.Constraints, err = p.parseTableConstraints(); err != nil {
|
|
return &stmt, err
|
|
}*/
|
|
|
|
if p.peek() != RP {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
stmt.Rparen, _, _ = p.scan()
|
|
|
|
//look for table options
|
|
if stmt.Options, err = p.parseTableOptions(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
/*case AS:
|
|
stmt.As, _, _ = p.scan()
|
|
if stmt.Select, err = p.parseSelectStatement(false, nil); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil*/
|
|
default:
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseTableOptions() (_ []TableOption, err error) {
|
|
if !isTableOptionStartToken(p.peek()) {
|
|
return nil, nil
|
|
}
|
|
|
|
var a []TableOption
|
|
|
|
for {
|
|
if !isTableOptionStartToken(p.peek()) {
|
|
return a, nil
|
|
}
|
|
cons, err := p.parseTableOption()
|
|
if cons != nil {
|
|
a = append(a, cons)
|
|
}
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseTableOption() (_ TableOption, err error) {
|
|
assert(isTableOptionStartToken(p.peek()))
|
|
|
|
var optionPos Pos
|
|
|
|
// Parse column constraints.
|
|
switch p.peek() {
|
|
case KEYPARTITIONS:
|
|
return p.parseKeyPartitionsOption(optionPos)
|
|
default:
|
|
assert(p.peek() == SHARDWIDTH)
|
|
return p.parseShardWidthOption(optionPos)
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseKeyPartitionsOption(optionPos Pos) (_ *KeyPartitionsOption, err error) {
|
|
assert(p.peek() == KEYPARTITIONS)
|
|
|
|
var opt KeyPartitionsOption
|
|
opt.KeyPartitions, _, _ = p.scan()
|
|
|
|
if isLiteralToken(p.peek()) {
|
|
opt.Expr = p.mustParseLiteral()
|
|
} else {
|
|
return &opt, p.errorExpected(p.pos, p.tok, "literal")
|
|
}
|
|
|
|
return &opt, nil
|
|
}
|
|
|
|
func (p *Parser) parseShardWidthOption(optionPos Pos) (_ *ShardWidthOption, err error) {
|
|
assert(p.peek() == SHARDWIDTH)
|
|
|
|
var opt ShardWidthOption
|
|
opt.ShardWidth, _, _ = p.scan()
|
|
|
|
if isLiteralToken(p.peek()) {
|
|
opt.Expr = p.mustParseLiteral()
|
|
} else {
|
|
return &opt, p.errorExpected(p.pos, p.tok, "literal")
|
|
}
|
|
|
|
return &opt, nil
|
|
}
|
|
|
|
func (p *Parser) parseColumnDefinitions() (_ []*ColumnDefinition, err error) {
|
|
var columns []*ColumnDefinition
|
|
for {
|
|
switch {
|
|
case isIdentToken(p.peek()):
|
|
col, err := p.parseColumnDefinition()
|
|
columns = append(columns, col)
|
|
if err != nil {
|
|
return columns, err
|
|
}
|
|
if p.peek() == COMMA {
|
|
p.scan()
|
|
}
|
|
case p.peek() == RP || isConstraintStartToken(p.peek(), true):
|
|
return columns, nil
|
|
default:
|
|
return columns, p.errorExpected(p.pos, p.tok, "column name, or right paren")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseColumnDefinition() (_ *ColumnDefinition, err error) {
|
|
var col ColumnDefinition
|
|
if col.Name, err = p.parseIdent("column name"); err != nil {
|
|
return &col, err
|
|
} else if col.Type, err = p.parseType(); err != nil {
|
|
return &col, err
|
|
}
|
|
|
|
if col.Constraints, err = p.parseColumnConstraints(); err != nil {
|
|
return &col, err
|
|
}
|
|
return &col, nil
|
|
}
|
|
|
|
/*func (p *Parser) parseTableConstraints() (_ []Constraint, err error) {
|
|
if !isConstraintStartToken(p.peek(), true) {
|
|
return nil, nil
|
|
}
|
|
|
|
var a []Constraint
|
|
for {
|
|
cons, err := p.parseConstraint(true)
|
|
if cons != nil {
|
|
a = append(a, cons)
|
|
}
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
|
|
// Scan delimiting comma.
|
|
if p.peek() != COMMA {
|
|
return a, nil
|
|
}
|
|
p.scan()
|
|
}
|
|
}*/
|
|
|
|
func (p *Parser) parseColumnConstraints() (_ []Constraint, err error) {
|
|
var a []Constraint
|
|
for isConstraintStartToken(p.peek(), false) {
|
|
cons, err := p.parseConstraint(false)
|
|
a = append(a, cons)
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func (p *Parser) parseConstraint(isTable bool) (_ Constraint, err error) {
|
|
assert(isConstraintStartToken(p.peek(), isTable))
|
|
|
|
var constraintPos Pos
|
|
var name *Ident
|
|
|
|
// Parse constraint name, if specified.
|
|
/*if p.peek() == CONSTRAINT {
|
|
constraintPos, _, _ = p.scan()
|
|
|
|
if name, err = p.parseIdent("constraint name"); err != nil {
|
|
return nil, err
|
|
}
|
|
}*/
|
|
|
|
// Table constraints only use a subset of column constraints.
|
|
/*if isTable {
|
|
switch p.peek() {
|
|
case PRIMARY:
|
|
return p.parsePrimaryKeyConstraint(constraintPos, name, isTable)
|
|
case UNIQUE:
|
|
return p.parseUniqueConstraint(constraintPos, name, isTable)
|
|
case CHECK:
|
|
return p.parseCheckConstraint(constraintPos, name)
|
|
default:
|
|
assert(p.peek() == FOREIGN)
|
|
return p.parseForeignKeyConstraint(constraintPos, name, isTable)
|
|
}
|
|
}*/
|
|
|
|
// Parse column constraints.
|
|
switch p.peek() {
|
|
//case PRIMARY:
|
|
// return p.parsePrimaryKeyConstraint(constraintPos, name, isTable)
|
|
//case NOT:
|
|
// return p.parseNotNullConstraint(constraintPos, name)
|
|
case MIN:
|
|
return p.parseMinConstraint(constraintPos, name)
|
|
case MAX:
|
|
return p.parseMaxConstraint(constraintPos, name)
|
|
case TIMEUNIT:
|
|
return p.parseTimeUnitConstraint(constraintPos, name)
|
|
case TIMEQUANTUM:
|
|
return p.parseTimeQuantumConstraint(constraintPos, name)
|
|
//case UNIQUE:
|
|
// return p.parseUniqueConstraint(constraintPos, name, isTable)
|
|
//case CHECK:
|
|
// return p.parseCheckConstraint(constraintPos, name)
|
|
//case DEFAULT:
|
|
// return p.parseDefaultConstraint(constraintPos, name)
|
|
default:
|
|
assert(p.peek() == CACHETYPE)
|
|
return p.parseCacheTypeConstraint(constraintPos, name)
|
|
}
|
|
}
|
|
|
|
/*func (p *Parser) parsePrimaryKeyConstraint(constraintPos Pos, name *Ident, isTable bool) (_ *PrimaryKeyConstraint, err error) {
|
|
assert(p.peek() == PRIMARY)
|
|
|
|
var cons PrimaryKeyConstraint
|
|
cons.Constraint = constraintPos
|
|
cons.Name = name
|
|
cons.Primary, _, _ = p.scan()
|
|
|
|
if p.peek() != KEY {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "KEY")
|
|
}
|
|
cons.Key, _, _ = p.scan()
|
|
|
|
// Table constraints specify columns; column constraints specify sort direction.
|
|
if isTable {
|
|
if p.peek() != LP {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
cons.Lparen, _, _ = p.scan()
|
|
|
|
for {
|
|
col, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &cons, err
|
|
}
|
|
cons.Columns = append(cons.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
cons.Rparen, _, _ = p.scan()
|
|
|
|
}
|
|
|
|
if !isTable {
|
|
if p.peek() == AUTOINCREMENT {
|
|
cons.Autoincrement, _, _ = p.scan()
|
|
}
|
|
}
|
|
return &cons, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseNotNullConstraint(constraintPos Pos, name *Ident) (_ *NotNullConstraint, err error) {
|
|
assert(p.peek() == NOT)
|
|
|
|
var cons NotNullConstraint
|
|
cons.Constraint = constraintPos
|
|
cons.Name = name
|
|
cons.Not, _, _ = p.scan()
|
|
|
|
if p.peek() != NULL {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "NULL")
|
|
}
|
|
cons.Null, _, _ = p.scan()
|
|
|
|
return &cons, nil
|
|
}*/
|
|
|
|
func (p *Parser) parseMinConstraint(constraintPos Pos, name *Ident) (_ *MinConstraint, err error) {
|
|
assert(p.peek() == MIN)
|
|
|
|
var cons MinConstraint
|
|
cons.Min, _, _ = p.scan()
|
|
|
|
// This parses an expression, as opposed to just a literal, because a
|
|
// negative value is a unary expression with Op = MINUS, so we need to allow
|
|
// for that.
|
|
expr, err := p.ParseExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cons.Expr = expr
|
|
|
|
return &cons, nil
|
|
}
|
|
|
|
func (p *Parser) parseMaxConstraint(constraintPos Pos, name *Ident) (_ *MaxConstraint, err error) {
|
|
assert(p.peek() == MAX)
|
|
|
|
var cons MaxConstraint
|
|
cons.Max, _, _ = p.scan()
|
|
|
|
// This parses an expression, as opposed to just a literal, because a
|
|
// negative value is a unary expression with Op = MINUS, so we need to allow
|
|
// for that.
|
|
expr, err := p.ParseExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cons.Expr = expr
|
|
|
|
return &cons, nil
|
|
}
|
|
|
|
func (p *Parser) parseCacheTypeConstraint(constraintPos Pos, name *Ident) (_ *CacheTypeConstraint, err error) {
|
|
assert(p.peek() == CACHETYPE)
|
|
|
|
var cons CacheTypeConstraint
|
|
cons.CacheType, _, _ = p.scan()
|
|
|
|
switch p.peek() {
|
|
case RANKED, LRU:
|
|
_, _, cacheTypeValue := p.scan()
|
|
// FeatureBase expects a lowercase cache type value.
|
|
cons.CacheTypeValue = strings.ToLower(cacheTypeValue)
|
|
default:
|
|
return &cons, p.errorExpected(p.pos, p.tok, "RANKED or LRU")
|
|
}
|
|
|
|
if p.peek() == SIZE {
|
|
cons.Size, _, _ = p.scan()
|
|
if isLiteralToken(p.peek()) {
|
|
cons.SizeExpr = p.mustParseLiteral()
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "literal")
|
|
}
|
|
}
|
|
|
|
return &cons, nil
|
|
}
|
|
|
|
func (p *Parser) parseTimeUnitConstraint(constraintPos Pos, name *Ident) (_ *TimeUnitConstraint, err error) {
|
|
assert(p.peek() == TIMEUNIT)
|
|
|
|
var cons TimeUnitConstraint
|
|
cons.TimeUnit, _, _ = p.scan()
|
|
|
|
if isLiteralToken(p.peek()) {
|
|
cons.Expr = p.mustParseLiteral()
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "literal")
|
|
}
|
|
if p.peek() == EPOCH {
|
|
cons.Epoch, _, _ = p.scan()
|
|
|
|
if isLiteralToken(p.peek()) {
|
|
cons.EpochExpr = p.mustParseLiteral()
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "literal")
|
|
}
|
|
}
|
|
return &cons, nil
|
|
}
|
|
|
|
func (p *Parser) parseTimeQuantumConstraint(constraintPos Pos, name *Ident) (_ *TimeQuantumConstraint, err error) {
|
|
assert(p.peek() == TIMEQUANTUM)
|
|
|
|
var cons TimeQuantumConstraint
|
|
cons.TimeQuantum, _, _ = p.scan()
|
|
|
|
if isLiteralToken(p.peek()) {
|
|
cons.Expr = p.mustParseLiteral()
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "literal")
|
|
}
|
|
if p.peek() == TTL {
|
|
cons.Ttl, _, _ = p.scan()
|
|
|
|
if isLiteralToken(p.peek()) {
|
|
cons.TtlExpr = p.mustParseLiteral()
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "literal")
|
|
}
|
|
}
|
|
return &cons, nil
|
|
}
|
|
|
|
/*func (p *Parser) parseUniqueConstraint(constraintPos Pos, name *Ident, isTable bool) (_ *UniqueConstraint, err error) {
|
|
assert(p.peek() == UNIQUE)
|
|
|
|
var cons UniqueConstraint
|
|
cons.Constraint = constraintPos
|
|
cons.Name = name
|
|
cons.Unique, _, _ = p.scan()
|
|
|
|
if isTable {
|
|
if p.peek() != LP {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
cons.Lparen, _, _ = p.scan()
|
|
|
|
for {
|
|
col, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &cons, err
|
|
}
|
|
cons.Columns = append(cons.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
cons.Rparen, _, _ = p.scan()
|
|
}
|
|
|
|
return &cons, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseCheckConstraint(constraintPos Pos, name *Ident) (_ *CheckConstraint, err error) {
|
|
assert(p.peek() == CHECK)
|
|
|
|
var cons CheckConstraint
|
|
cons.Constraint = constraintPos
|
|
cons.Name = name
|
|
cons.Check, _, _ = p.scan()
|
|
|
|
if p.peek() != LP {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
cons.Lparen, _, _ = p.scan()
|
|
|
|
if cons.Expr, err = p.ParseExpr(); err != nil {
|
|
return &cons, err
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
cons.Rparen, _, _ = p.scan()
|
|
|
|
return &cons, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseDefaultConstraint(constraintPos Pos, name *Ident) (_ *DefaultConstraint, err error) {
|
|
assert(p.peek() == DEFAULT)
|
|
|
|
var cons DefaultConstraint
|
|
cons.Constraint = constraintPos
|
|
cons.Name = name
|
|
cons.Default, _, _ = p.scan()
|
|
if isLiteralToken(p.peek()) {
|
|
cons.Expr = p.mustParseLiteral()
|
|
} else if p.peek() == PLUS || p.peek() == MINUS {
|
|
if cons.Expr, err = p.parseSignedNumber("signed number"); err != nil {
|
|
return &cons, err
|
|
}
|
|
} else {
|
|
if p.peek() != LP {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "literal value or left paren")
|
|
}
|
|
cons.Lparen, _, _ = p.scan()
|
|
|
|
if cons.Expr, err = p.ParseExpr(); err != nil {
|
|
return &cons, err
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
cons.Rparen, _, _ = p.scan()
|
|
}
|
|
return &cons, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseForeignKeyConstraint(constraintPos Pos, name *Ident, isTable bool) (_ *ForeignKeyConstraint, err error) {
|
|
var cons ForeignKeyConstraint
|
|
cons.Constraint = constraintPos
|
|
cons.Name = name
|
|
|
|
// Table constraints start with "FOREIGN KEY (col1, col2, etc)".
|
|
if isTable {
|
|
assert(p.peek() == FOREIGN)
|
|
cons.Foreign, _, _ = p.scan()
|
|
|
|
if p.peek() != KEY {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "KEY")
|
|
}
|
|
cons.ForeignKey, _, _ = p.scan()
|
|
|
|
if p.peek() != LP {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
cons.Lparen, _, _ = p.scan()
|
|
|
|
for {
|
|
col, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &cons, err
|
|
}
|
|
cons.Columns = append(cons.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
cons.Rparen, _, _ = p.scan()
|
|
}
|
|
|
|
if p.peek() != REFERENCES {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "REFERENCES")
|
|
}
|
|
cons.References, _, _ = p.scan()
|
|
|
|
if cons.ForeignTable, err = p.parseIdent("foreign table name"); err != nil {
|
|
return &cons, err
|
|
}
|
|
|
|
// Parse column list.
|
|
if p.peek() != LP {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
cons.ForeignLparen, _, _ = p.scan()
|
|
|
|
for {
|
|
col, err := p.parseIdent("foreign column name")
|
|
if err != nil {
|
|
return &cons, err
|
|
}
|
|
cons.ForeignColumns = append(cons.ForeignColumns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
|
|
cons.ForeignRparen, _, _ = p.scan()
|
|
|
|
// Parse foreign key args.
|
|
for p.peek() == ON {
|
|
var arg ForeignKeyArg
|
|
arg.On, _, _ = p.scan()
|
|
|
|
// Parse foreign key type.
|
|
if p.peek() == UPDATE {
|
|
arg.OnUpdate, _, _ = p.scan()
|
|
} else if p.peek() == DELETE {
|
|
arg.OnDelete, _, _ = p.scan()
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "UPDATE or DELETE")
|
|
}
|
|
|
|
// Parse foreign key action.
|
|
if p.peek() == SET {
|
|
arg.Set, _, _ = p.scan()
|
|
if p.peek() == NULL {
|
|
arg.SetNull, _, _ = p.scan()
|
|
} else if p.peek() == DEFAULT {
|
|
arg.SetDefault, _, _ = p.scan()
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "NULL or DEFAULT")
|
|
}
|
|
} else if p.peek() == CASCADE {
|
|
arg.Cascade, _, _ = p.scan()
|
|
} else if p.peek() == RESTRICT {
|
|
arg.Restrict, _, _ = p.scan()
|
|
} else if p.peek() == NO {
|
|
arg.No, _, _ = p.scan()
|
|
if p.peek() == ACTION {
|
|
arg.NoAction, _, _ = p.scan()
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "ACTION")
|
|
}
|
|
} else {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "SET NULL, SET DEFAULT, CASCADE, RESTRICT, or NO ACTION")
|
|
}
|
|
|
|
cons.Args = append(cons.Args, &arg)
|
|
}
|
|
|
|
// Parse deferrable subclause.
|
|
if p.peek() == NOT || p.peek() == DEFERRABLE {
|
|
if p.peek() == NOT {
|
|
cons.Not, _, _ = p.scan()
|
|
if p.peek() != DEFERRABLE {
|
|
return &cons, p.errorExpected(p.pos, p.tok, "DEFERRABLE")
|
|
}
|
|
cons.NotDeferrable, _, _ = p.scan()
|
|
} else {
|
|
cons.Deferrable, _, _ = p.scan()
|
|
}
|
|
|
|
if p.peek() == INITIALLY {
|
|
cons.Initially, _, _ = p.scan()
|
|
if p.peek() == DEFERRED {
|
|
cons.InitiallyDeferred, _, _ = p.scan()
|
|
} else if p.peek() == IMMEDIATE {
|
|
cons.InitiallyImmediate, _, _ = p.scan()
|
|
}
|
|
}
|
|
}
|
|
|
|
return &cons, nil
|
|
}*/
|
|
|
|
func (p *Parser) parseDropTableStatement(dropPos Pos) (_ *DropTableStatement, err error) {
|
|
assert(p.peek() == TABLE)
|
|
|
|
var stmt DropTableStatement
|
|
stmt.Drop = dropPos
|
|
stmt.Table, _, _ = p.scan()
|
|
|
|
// Parse optional "IF EXISTS".
|
|
if p.peek() == IF {
|
|
stmt.If, _, _ = p.scan()
|
|
if p.peek() != EXISTS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "EXISTS")
|
|
}
|
|
stmt.IfExists, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("table name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
return &stmt, nil
|
|
}
|
|
|
|
/*func (p *Parser) parseCreateViewStatement(createPos Pos) (_ *CreateViewStatement, err error) {
|
|
assert(p.peek() == VIEW)
|
|
|
|
var stmt CreateViewStatement
|
|
stmt.Create = createPos
|
|
stmt.View, _, _ = p.scan()
|
|
|
|
// Parse optional "IF NOT EXISTS".
|
|
if p.peek() == IF {
|
|
stmt.If, _, _ = p.scan()
|
|
|
|
if p.peek() != NOT {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "NOT")
|
|
}
|
|
stmt.IfNot, _, _ = p.scan()
|
|
|
|
if p.peek() != EXISTS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "EXISTS")
|
|
}
|
|
stmt.IfNotExists, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("view name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
// Parse optional column list.
|
|
if p.peek() == LP {
|
|
stmt.Lparen, _, _ = p.scan()
|
|
for {
|
|
col, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.Columns = append(stmt.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
stmt.Rparen, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse "AS select-stmt"
|
|
if p.peek() != AS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "AS")
|
|
}
|
|
stmt.As, _, _ = p.scan()
|
|
if stmt.Select, err = p.parseSelectStatement(false, nil); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseDropViewStatement(dropPos Pos) (_ *DropViewStatement, err error) {
|
|
assert(p.peek() == VIEW)
|
|
|
|
var stmt DropViewStatement
|
|
stmt.Drop = dropPos
|
|
stmt.View, _, _ = p.scan()
|
|
|
|
// Parse optional "IF EXISTS".
|
|
if p.peek() == IF {
|
|
stmt.If, _, _ = p.scan()
|
|
if p.peek() != EXISTS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "EXISTS")
|
|
}
|
|
stmt.IfExists, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("view name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseCreateIndexStatement(createPos Pos) (_ *CreateIndexStatement, err error) {
|
|
assert(p.peek() == INDEX || p.peek() == UNIQUE)
|
|
|
|
var stmt CreateIndexStatement
|
|
stmt.Create = createPos
|
|
if p.peek() == UNIQUE {
|
|
stmt.Unique, _, _ = p.scan()
|
|
}
|
|
if p.peek() != INDEX {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "INDEX")
|
|
}
|
|
stmt.Index, _, _ = p.scan()
|
|
|
|
// Parse optional "IF NOT EXISTS".
|
|
if p.peek() == IF {
|
|
stmt.If, _, _ = p.scan()
|
|
|
|
if p.peek() != NOT {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "NOT")
|
|
}
|
|
stmt.IfNot, _, _ = p.scan()
|
|
|
|
if p.peek() != EXISTS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "EXISTS")
|
|
}
|
|
stmt.IfNotExists, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("index name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
if p.peek() != ON {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "ON")
|
|
}
|
|
stmt.On, _, _ = p.scan()
|
|
|
|
if stmt.Table, err = p.parseIdent("table name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
// Parse optional column list.
|
|
if p.peek() != LP {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
stmt.Lparen, _, _ = p.scan()
|
|
for {
|
|
col, err := p.parseIndexedColumn()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.Columns = append(stmt.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
stmt.Rparen, _, _ = p.scan()
|
|
|
|
// Parse optional "WHERE expr"
|
|
if p.peek() == WHERE {
|
|
stmt.Where, _, _ = p.scan()
|
|
if stmt.WhereExpr, err = p.ParseExpr(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseDropIndexStatement(dropPos Pos) (_ *DropIndexStatement, err error) {
|
|
assert(p.peek() == INDEX)
|
|
|
|
var stmt DropIndexStatement
|
|
stmt.Drop = dropPos
|
|
stmt.Index, _, _ = p.scan()
|
|
|
|
// Parse optional "IF EXISTS".
|
|
if p.peek() == IF {
|
|
stmt.If, _, _ = p.scan()
|
|
if p.peek() != EXISTS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "EXISTS")
|
|
}
|
|
stmt.IfExists, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("index name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseCreateTriggerStatement(createPos Pos) (_ *CreateTriggerStatement, err error) {
|
|
assert(p.peek() == TRIGGER)
|
|
|
|
var stmt CreateTriggerStatement
|
|
stmt.Create = createPos
|
|
stmt.Trigger, _, _ = p.scan()
|
|
|
|
// Parse optional "IF NOT EXISTS".
|
|
if p.peek() == IF {
|
|
stmt.If, _, _ = p.scan()
|
|
|
|
if p.peek() != NOT {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "NOT")
|
|
}
|
|
stmt.IfNot, _, _ = p.scan()
|
|
|
|
if p.peek() != EXISTS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "EXISTS")
|
|
}
|
|
stmt.IfNotExists, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("index name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
// Parse BEFORE, AFTER, or INSTEAD OF
|
|
switch p.peek() {
|
|
case BEFORE:
|
|
stmt.Before, _, _ = p.scan()
|
|
case AFTER:
|
|
stmt.After, _, _ = p.scan()
|
|
case INSTEAD:
|
|
stmt.Instead, _, _ = p.scan()
|
|
if p.peek() != OF {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "OF")
|
|
}
|
|
stmt.InsteadOf, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse DELETE, INSERT, UPDATE, or UPDATE OF [columns]
|
|
switch p.peek() {
|
|
case DELETE:
|
|
stmt.Delete, _, _ = p.scan()
|
|
case INSERT:
|
|
stmt.Insert, _, _ = p.scan()
|
|
case UPDATE:
|
|
stmt.Update, _, _ = p.scan()
|
|
if p.peek() == OF {
|
|
stmt.UpdateOf, _, _ = p.scan()
|
|
for {
|
|
col, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.UpdateOfColumns = append(stmt.UpdateOfColumns, col)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
}
|
|
default:
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "DELETE, INSERT, or UPDATE")
|
|
}
|
|
|
|
// Parse "ON table-name".
|
|
if p.peek() != ON {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "ON")
|
|
}
|
|
stmt.On, _, _ = p.scan()
|
|
if stmt.Table, err = p.parseIdent("table name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
// Parse optional "FOR EACH ROW".
|
|
if p.peek() == FOR {
|
|
stmt.For, _, _ = p.scan()
|
|
if p.peek() != EACH {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "EACH")
|
|
}
|
|
stmt.ForEach, _, _ = p.scan()
|
|
if p.peek() != ROW {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "ROW")
|
|
}
|
|
stmt.ForEachRow, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse optional "WHEN expr".
|
|
if p.peek() == WHEN {
|
|
stmt.When, _, _ = p.scan()
|
|
if stmt.WhenExpr, err = p.ParseExpr(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
|
|
// Parse trigger body.
|
|
if p.peek() != BEGIN {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "BEGIN")
|
|
}
|
|
stmt.Begin, _, _ = p.scan()
|
|
|
|
for {
|
|
s, err := p.parseTriggerBodyStatement()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.Body = append(stmt.Body, s)
|
|
|
|
if p.peek() == END {
|
|
break
|
|
}
|
|
}
|
|
stmt.End, _, _ = p.scan()
|
|
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseTriggerBodyStatement() (stmt Statement, err error) {
|
|
switch p.peek() {
|
|
case SELECT, VALUES:
|
|
stmt, err = p.parseSelectStatement(false, nil)
|
|
case INSERT, REPLACE:
|
|
stmt, err = p.parseInsertStatement(nil)
|
|
case UPDATE:
|
|
stmt, err = p.parseUpdateStatement(nil)
|
|
case DELETE:
|
|
stmt, err = p.parseDeleteStatement(nil)
|
|
//case WITH:
|
|
// stmt, err = p.parseWithStatement()
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, "statement")
|
|
}
|
|
if err != nil {
|
|
return stmt, err
|
|
}
|
|
|
|
// Ensure trailing semicolon exists.
|
|
if p.peek() != SEMI {
|
|
return stmt, p.errorExpected(p.pos, p.tok, "semicolon")
|
|
}
|
|
p.scan()
|
|
|
|
return stmt, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseDropTriggerStatement(dropPos Pos) (_ *DropTriggerStatement, err error) {
|
|
assert(p.peek() == TRIGGER)
|
|
|
|
var stmt DropTriggerStatement
|
|
stmt.Drop = dropPos
|
|
stmt.Trigger, _, _ = p.scan()
|
|
|
|
// Parse optional "IF EXISTS".
|
|
if p.peek() == IF {
|
|
stmt.If, _, _ = p.scan()
|
|
if p.peek() != EXISTS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "EXISTS")
|
|
}
|
|
stmt.IfExists, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Name, err = p.parseIdent("trigger name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
func (p *Parser) parseIdent(desc string) (*Ident, error) {
|
|
pos, tok, lit := p.scan()
|
|
switch tok {
|
|
case IDENT, QIDENT:
|
|
return &Ident{Name: lit, NamePos: pos, Quoted: tok == QIDENT}, nil
|
|
default:
|
|
return nil, p.errorExpected(pos, tok, desc)
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseType() (_ *Type, err error) {
|
|
var typ Type
|
|
if typ.Name, err = p.parseIdent("type name"); err != nil {
|
|
return &typ, err
|
|
}
|
|
|
|
// Optionally parse scale.
|
|
if p.peek() == LP {
|
|
typ.Lparen, _, _ = p.scan()
|
|
if typ.Scale, err = p.parseIntegerLiteral("scale"); err != nil {
|
|
return &typ, err
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return nil, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
typ.Rparen, _, _ = p.scan()
|
|
}
|
|
|
|
return &typ, nil
|
|
}
|
|
|
|
func (p *Parser) parseBulkInsertStatement() (_ *BulkInsertStatement, err error) {
|
|
if p.peek() != BULK {
|
|
return nil, p.errorExpected(p.pos, p.tok, "BULK")
|
|
}
|
|
|
|
var stmt BulkInsertStatement
|
|
|
|
stmt.Bulk, _, _ = p.scan()
|
|
|
|
if p.peek() != INSERT {
|
|
return nil, p.errorExpected(p.pos, p.tok, "INSERT")
|
|
}
|
|
stmt.Insert, _, _ = p.scan()
|
|
|
|
// Parse table name & optional alias.
|
|
if stmt.Table, err = p.parseIdent("table name"); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if p.peek() != FROM {
|
|
return nil, p.errorExpected(p.pos, p.tok, "FROM")
|
|
}
|
|
stmt.From, _, _ = p.scan()
|
|
|
|
if isLiteralToken(p.peek()) {
|
|
stmt.DataFile = p.mustParseLiteral()
|
|
} else {
|
|
return nil, p.errorExpected(p.pos, p.tok, "literal")
|
|
}
|
|
|
|
return &stmt, nil
|
|
}
|
|
|
|
func (p *Parser) parseInsertStatement(withClause *WithClause) (_ *InsertStatement, err error) {
|
|
if pk := p.peek(); pk != INSERT && pk != REPLACE {
|
|
return nil, p.errorExpected(p.pos, p.tok, "INSERT or REPLACE")
|
|
}
|
|
|
|
var stmt InsertStatement
|
|
//stmt.WithClause = withClause
|
|
|
|
if p.peek() == INSERT {
|
|
stmt.Insert, _, _ = p.scan()
|
|
|
|
if p.peek() == OR {
|
|
stmt.InsertOr, _, _ = p.scan()
|
|
|
|
switch p.peek() {
|
|
//case ROLLBACK:
|
|
// stmt.InsertOrRollback, _, _ = p.scan()
|
|
case REPLACE:
|
|
stmt.InsertOrReplace, _, _ = p.scan()
|
|
//case ABORT:
|
|
// stmt.InsertOrAbort, _, _ = p.scan()
|
|
//case FAIL:
|
|
// stmt.InsertOrFail, _, _ = p.scan()
|
|
//case IGNORE:
|
|
// stmt.InsertOrIgnore, _, _ = p.scan()
|
|
default:
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "REPLACE")
|
|
}
|
|
}
|
|
} else {
|
|
stmt.Replace, _, _ = p.scan()
|
|
}
|
|
|
|
if p.peek() != INTO {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "INTO")
|
|
}
|
|
stmt.Into, _, _ = p.scan()
|
|
|
|
// Parse table name & optional alias.
|
|
if stmt.Table, err = p.parseIdent("table name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
if p.peek() == AS {
|
|
stmt.As, _, _ = p.scan()
|
|
if stmt.Alias, err = p.parseIdent("alias"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
|
|
// Parse optional column list.
|
|
if p.peek() == LP {
|
|
stmt.ColumnsLparen, _, _ = p.scan()
|
|
for {
|
|
col, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.Columns = append(stmt.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
stmt.ColumnsRparen, _, _ = p.scan()
|
|
}
|
|
|
|
switch p.peek() {
|
|
case VALUES:
|
|
stmt.Values, _, _ = p.scan()
|
|
for {
|
|
var list ExprList
|
|
if p.peek() != LP {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
list.Lparen, _, _ = p.scan()
|
|
|
|
for {
|
|
expr, err := p.ParseExpr()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
list.Exprs = append(list.Exprs, expr)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
list.Rparen, _, _ = p.scan()
|
|
stmt.ValueList = &list
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
//case SELECT:
|
|
// if stmt.Select, err = p.parseSelectStatement(false, nil); err != nil {
|
|
// return &stmt, err
|
|
// }
|
|
//case DEFAULT:
|
|
// stmt.Default, _, _ = p.scan()
|
|
// if p.peek() != VALUES {
|
|
// return &stmt, p.errorExpected(p.pos, p.tok, "VALUES")
|
|
// }
|
|
// stmt.DefaultValues, _, _ = p.scan()
|
|
default:
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "VALUES")
|
|
}
|
|
|
|
// Parse optional upsert clause.
|
|
//if p.peek() == ON {
|
|
// if stmt.UpsertClause, err = p.parseUpsertClause(); err != nil {
|
|
// return &stmt, err
|
|
// }
|
|
//}
|
|
|
|
return &stmt, nil
|
|
}
|
|
|
|
/*func (p *Parser) parseUpsertClause() (_ *UpsertClause, err error) {
|
|
assert(p.peek() == ON)
|
|
|
|
var clause UpsertClause
|
|
|
|
// Parse "ON CONFLICT"
|
|
clause.On, _, _ = p.scan()
|
|
if p.peek() != CONFLICT {
|
|
return &clause, p.errorExpected(p.pos, p.tok, "CONFLICT")
|
|
}
|
|
clause.OnConflict, _, _ = p.scan()
|
|
|
|
// Parse optional indexed column list & WHERE conditional.
|
|
if p.peek() == LP {
|
|
clause.Lparen, _, _ = p.scan()
|
|
for {
|
|
col, err := p.parseIndexedColumn()
|
|
if err != nil {
|
|
return &clause, err
|
|
}
|
|
clause.Columns = append(clause.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &clause, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
clause.Rparen, _, _ = p.scan()
|
|
|
|
if p.peek() == WHERE {
|
|
clause.Where, _, _ = p.scan()
|
|
if clause.WhereExpr, err = p.ParseExpr(); err != nil {
|
|
return &clause, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse "DO NOTHING" or "DO UPDATE SET".
|
|
if p.peek() != DO {
|
|
return &clause, p.errorExpected(p.pos, p.tok, "DO")
|
|
}
|
|
clause.Do, _, _ = p.scan()
|
|
|
|
// If next token is NOTHING, then read it and exit immediately.
|
|
if p.peek() == NOTHING {
|
|
clause.DoNothing, _, _ = p.scan()
|
|
return &clause, nil
|
|
} else if p.peek() != UPDATE {
|
|
return &clause, p.errorExpected(p.pos, p.tok, "NOTHING or UPDATE SET")
|
|
}
|
|
|
|
// Otherwise parse "UPDATE SET"
|
|
clause.DoUpdate, _, _ = p.scan()
|
|
if p.peek() != SET {
|
|
return &clause, p.errorExpected(p.pos, p.tok, "SET")
|
|
}
|
|
clause.DoUpdateSet, _, _ = p.scan()
|
|
|
|
// Parse list of assignments.
|
|
for {
|
|
assignment, err := p.parseAssignment()
|
|
if err != nil {
|
|
return &clause, err
|
|
}
|
|
clause.Assignments = append(clause.Assignments, assignment)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
|
|
// Parse WHERE after DO UPDATE SET.
|
|
if p.peek() == WHERE {
|
|
clause.UpdateWhere, _, _ = p.scan()
|
|
if clause.UpdateWhereExpr, err = p.ParseExpr(); err != nil {
|
|
return &clause, err
|
|
}
|
|
}
|
|
|
|
return &clause, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseIndexedColumn() (_ *IndexedColumn, err error) {
|
|
var col IndexedColumn
|
|
if col.X, err = p.ParseExpr(); err != nil {
|
|
return &col, err
|
|
}
|
|
if p.peek() == ASC {
|
|
col.Asc, _, _ = p.scan()
|
|
} else if p.peek() == DESC {
|
|
col.Desc, _, _ = p.scan()
|
|
}
|
|
return &col, nil
|
|
}*/
|
|
|
|
func (p *Parser) parseUpdateStatement(withClause *WithClause) (_ *UpdateStatement, err error) {
|
|
assert(p.peek() == UPDATE)
|
|
|
|
var stmt UpdateStatement
|
|
stmt.WithClause = withClause
|
|
|
|
stmt.Update, _, _ = p.scan()
|
|
if p.peek() == OR {
|
|
stmt.UpdateOr, _, _ = p.scan()
|
|
|
|
switch p.peek() {
|
|
case ROLLBACK:
|
|
stmt.UpdateOrRollback, _, _ = p.scan()
|
|
case REPLACE:
|
|
stmt.UpdateOrReplace, _, _ = p.scan()
|
|
case ABORT:
|
|
stmt.UpdateOrAbort, _, _ = p.scan()
|
|
case FAIL:
|
|
stmt.UpdateOrFail, _, _ = p.scan()
|
|
case IGNORE:
|
|
stmt.UpdateOrIgnore, _, _ = p.scan()
|
|
default:
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "ROLLBACK, REPLACE, ABORT, FAIL, or IGNORE")
|
|
}
|
|
}
|
|
|
|
if stmt.Table, err = p.parseQualifiedTableName(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
// Parse SET + list of assignments.
|
|
if p.peek() != SET {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "SET")
|
|
}
|
|
stmt.Set, _, _ = p.scan()
|
|
|
|
for {
|
|
assignment, err := p.parseAssignment()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.Assignments = append(stmt.Assignments, assignment)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
|
|
// Parse WHERE clause.
|
|
if p.peek() == WHERE {
|
|
stmt.Where, _, _ = p.scan()
|
|
if stmt.WhereExpr, err = p.ParseExpr(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
|
|
return &stmt, nil
|
|
}
|
|
|
|
func (p *Parser) parseDeleteStatement(withClause *WithClause) (_ *DeleteStatement, err error) {
|
|
assert(p.peek() == DELETE)
|
|
|
|
var stmt DeleteStatement
|
|
stmt.WithClause = withClause
|
|
|
|
// Parse "DELETE FROM tbl"
|
|
stmt.Delete, _, _ = p.scan()
|
|
if p.peek() != FROM {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "FROM")
|
|
}
|
|
stmt.From, _, _ = p.scan()
|
|
if stmt.Table, err = p.parseQualifiedTableName(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
// Parse WHERE clause.
|
|
if p.peek() == WHERE {
|
|
stmt.Where, _, _ = p.scan()
|
|
if stmt.WhereExpr, err = p.ParseExpr(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
|
|
// Parse ORDER BY clause. This differs from the SELECT parsing in that
|
|
// if an ORDER BY is specified then the LIMIT is required.
|
|
if p.peek() == ORDER {
|
|
if p.peek() == ORDER {
|
|
stmt.Order, _, _ = p.scan()
|
|
if p.peek() != BY {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "BY")
|
|
}
|
|
stmt.OrderBy, _, _ = p.scan()
|
|
|
|
for {
|
|
term, err := p.parseOrderingTerm()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.OrderingTerms = append(stmt.OrderingTerms, term)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
}
|
|
}
|
|
|
|
return &stmt, nil
|
|
}
|
|
|
|
func (p *Parser) parseAssignment() (_ *Assignment, err error) {
|
|
var assignment Assignment
|
|
|
|
// Parse either a single column (IDENT) or a column list (LP IDENT COMMA IDENT RP)
|
|
if isIdentToken(p.peek()) {
|
|
col, _ := p.parseIdent("column name")
|
|
assignment.Columns = []*Ident{col}
|
|
} else if p.peek() == LP {
|
|
assignment.Lparen, _, _ = p.scan()
|
|
for {
|
|
col, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &assignment, err
|
|
}
|
|
assignment.Columns = append(assignment.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &assignment, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
assignment.Rparen, _, _ = p.scan()
|
|
} else {
|
|
return &assignment, p.errorExpected(p.pos, p.tok, "column name or column list")
|
|
}
|
|
|
|
if p.peek() != EQ {
|
|
return &assignment, p.errorExpected(p.pos, p.tok, "=")
|
|
}
|
|
assignment.Eq, _, _ = p.scan()
|
|
|
|
if assignment.Expr, err = p.ParseExpr(); err != nil {
|
|
return &assignment, err
|
|
}
|
|
|
|
return &assignment, nil
|
|
}
|
|
|
|
// parseSelectStatement parses a SELECT statement.
|
|
// If compounded is true, some parts of the SELECT syntax are skipped.
|
|
func (p *Parser) parseSelectStatement(compounded bool, withClause *WithClause) (_ *SelectStatement, err error) {
|
|
var stmt SelectStatement
|
|
//stmt.WithClause = withClause
|
|
|
|
// Parse optional "WITH [RECURSIVE} cte, cte..."
|
|
// This is only called here if this method is called directly. Generic
|
|
// statement parsing will parse the WITH clause and pass it in instead.
|
|
//if !compounded && stmt.WithClause == nil && p.peek() == WITH {
|
|
// if stmt.WithClause, err = p.parseWithClause(); err != nil {
|
|
// return &stmt, err
|
|
// }
|
|
//}
|
|
|
|
switch p.peek() {
|
|
/*case VALUES:
|
|
stmt.Values, _, _ = p.scan()
|
|
|
|
for {
|
|
var list ExprList
|
|
if p.peek() != LP {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
list.Lparen, _, _ = p.scan()
|
|
|
|
for {
|
|
expr, err := p.ParseExpr()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
list.Exprs = append(list.Exprs, expr)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
list.Rparen, _, _ = p.scan()
|
|
stmt.ValueLists = append(stmt.ValueLists, &list)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
|
|
}*/
|
|
|
|
case SELECT:
|
|
stmt.Select, _, _ = p.scan()
|
|
|
|
// Parse optional "DISTINCT".
|
|
if tok := p.peek(); tok == DISTINCT {
|
|
stmt.Distinct, _, _ = p.scan()
|
|
}
|
|
|
|
if p.peek() == TOP {
|
|
stmt.Top, _, _ = p.scan()
|
|
if p.peek() == LP {
|
|
_, _, _ = p.scan()
|
|
}
|
|
if stmt.TopExpr, err = p.ParseExpr(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
if p.peek() == RP {
|
|
_, _, _ = p.scan()
|
|
}
|
|
}
|
|
|
|
if p.peek() == TOPN {
|
|
stmt.TopN, _, _ = p.scan()
|
|
if p.peek() == LP {
|
|
_, _, _ = p.scan()
|
|
}
|
|
if stmt.TopExpr, err = p.ParseExpr(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
if p.peek() == RP {
|
|
_, _, _ = p.scan()
|
|
}
|
|
}
|
|
|
|
// Parse result columns.
|
|
for {
|
|
col, err := p.parseResultColumn()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.Columns = append(stmt.Columns, col)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
|
|
// Parse FROM clause.
|
|
if p.peek() == FROM {
|
|
stmt.From, _, _ = p.scan()
|
|
if stmt.Source, err = p.parseSource(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
|
|
// Parse WHERE clause.
|
|
if p.peek() == WHERE {
|
|
stmt.Where, _, _ = p.scan()
|
|
if stmt.WhereExpr, err = p.ParseExpr(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
|
|
// Parse GROUP BY/HAVING clause.
|
|
if p.peek() == GROUP {
|
|
stmt.Group, _, _ = p.scan()
|
|
if p.peek() != BY {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "BY")
|
|
}
|
|
stmt.GroupBy, _, _ = p.scan()
|
|
|
|
for {
|
|
expr, err := p.ParseExpr()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.GroupByExprs = append(stmt.GroupByExprs, expr)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
|
|
// Parse optional HAVING clause.
|
|
if p.peek() == HAVING {
|
|
stmt.Having, _, _ = p.scan()
|
|
if stmt.HavingExpr, err = p.ParseExpr(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse WINDOW clause.
|
|
if p.peek() == WINDOW {
|
|
stmt.Window, _, _ = p.scan()
|
|
|
|
for {
|
|
var window Window
|
|
if window.Name, err = p.parseIdent("window name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
if p.peek() != AS {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "AS")
|
|
}
|
|
window.As, _, _ = p.scan()
|
|
|
|
if window.Definition, err = p.parseWindowDefinition(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
stmt.Windows = append(stmt.Windows, &window)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
}
|
|
default:
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "SELECT")
|
|
}
|
|
|
|
// Optionally compound additional SELECT/VALUES.
|
|
switch tok := p.peek(); tok {
|
|
case UNION, INTERSECT, EXCEPT:
|
|
if tok == UNION {
|
|
stmt.Union, _, _ = p.scan()
|
|
if p.peek() == ALL {
|
|
stmt.UnionAll, _, _ = p.scan()
|
|
}
|
|
} else if tok == INTERSECT {
|
|
stmt.Intersect, _, _ = p.scan()
|
|
} else {
|
|
stmt.Except, _, _ = p.scan()
|
|
}
|
|
|
|
if stmt.Compound, err = p.parseSelectStatement(true, nil); err != nil {
|
|
return &stmt, err
|
|
}
|
|
}
|
|
|
|
// Parse ORDER BY clause.
|
|
if !compounded && p.peek() == ORDER {
|
|
stmt.Order, _, _ = p.scan()
|
|
if p.peek() != BY {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "BY")
|
|
}
|
|
stmt.OrderBy, _, _ = p.scan()
|
|
|
|
for {
|
|
term, err := p.parseOrderingTerm()
|
|
if err != nil {
|
|
return &stmt, err
|
|
}
|
|
stmt.OrderingTerms = append(stmt.OrderingTerms, term)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
}
|
|
|
|
return &stmt, nil
|
|
}
|
|
|
|
func (p *Parser) parseResultColumn() (_ *ResultColumn, err error) {
|
|
var col ResultColumn
|
|
|
|
// An initial "*" returns all columns.
|
|
if p.peek() == STAR {
|
|
col.Star, _, _ = p.scan()
|
|
return &col, nil
|
|
}
|
|
|
|
// Next can be either "EXPR [[AS] column-alias]" or "IDENT DOT STAR".
|
|
// We need read the next element as an expression and then determine what next.
|
|
if col.Expr, err = p.ParseExpr(); err != nil {
|
|
return &col, err
|
|
}
|
|
|
|
// If we have a qualified ref w/ a star, don't allow an alias.
|
|
if ref, ok := col.Expr.(*QualifiedRef); ok && ref.Star.IsValid() {
|
|
return &col, nil
|
|
}
|
|
|
|
// If "AS" is next, the alias must follow.
|
|
// Otherwise it can optionally be an IDENT alias.
|
|
if p.peek() == AS {
|
|
col.As, _, _ = p.scan()
|
|
if !isIdentToken(p.peek()) {
|
|
return &col, p.errorExpected(p.pos, p.tok, "column alias")
|
|
}
|
|
col.Alias, _ = p.parseIdent("column alias")
|
|
} else if isIdentToken(p.peek()) {
|
|
col.Alias, _ = p.parseIdent("column alias")
|
|
}
|
|
|
|
return &col, nil
|
|
}
|
|
|
|
func (p *Parser) parseSource() (source Source, err error) {
|
|
source, err = p.parseUnarySource()
|
|
if err != nil {
|
|
return source, err
|
|
}
|
|
|
|
for {
|
|
// Exit immediately if not part of a join operator.
|
|
switch p.peek() {
|
|
case COMMA, NATURAL, LEFT, INNER, CROSS, JOIN:
|
|
default:
|
|
return source, nil
|
|
}
|
|
|
|
// Parse join operator.
|
|
operator, err := p.parseJoinOperator()
|
|
if err != nil {
|
|
return source, err
|
|
}
|
|
y, err := p.parseUnarySource()
|
|
if err != nil {
|
|
return source, err
|
|
}
|
|
constraint, err := p.parseJoinConstraint()
|
|
if err != nil {
|
|
return source, err
|
|
}
|
|
|
|
// Rewrite last source to nest next join on right side.
|
|
if lhs, ok := source.(*JoinClause); ok {
|
|
source = &JoinClause{
|
|
X: lhs.X,
|
|
Operator: lhs.Operator,
|
|
Y: &JoinClause{
|
|
X: lhs.Y,
|
|
Operator: operator,
|
|
Y: y,
|
|
Constraint: constraint,
|
|
},
|
|
Constraint: lhs.Constraint,
|
|
}
|
|
} else {
|
|
source = &JoinClause{X: source, Operator: operator, Y: y, Constraint: constraint}
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseUnarySource parses a quailfied table name or subquery but not a JOIN.
|
|
func (p *Parser) parseUnarySource() (source Source, err error) {
|
|
switch p.peek() {
|
|
case LP:
|
|
return p.parseParenSource()
|
|
case IDENT, QIDENT:
|
|
return p.parseQualifiedTableName()
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, "table name or left paren")
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseJoinOperator() (*JoinOperator, error) {
|
|
var op JoinOperator
|
|
|
|
// Handle single comma join.
|
|
if p.peek() == COMMA {
|
|
op.Comma, _, _ = p.scan()
|
|
return &op, nil
|
|
}
|
|
|
|
if p.peek() == NATURAL {
|
|
op.Natural, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse "LEFT", "LEFT OUTER", "INNER", or "CROSS"
|
|
switch p.peek() {
|
|
case LEFT:
|
|
op.Left, _, _ = p.scan()
|
|
if p.peek() == OUTER {
|
|
op.Outer, _, _ = p.scan()
|
|
}
|
|
case INNER:
|
|
op.Inner, _, _ = p.scan()
|
|
case CROSS:
|
|
op.Cross, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse final JOIN.
|
|
if p.peek() != JOIN {
|
|
return &op, p.errorExpected(p.pos, p.tok, "JOIN")
|
|
}
|
|
op.Join, _, _ = p.scan()
|
|
|
|
return &op, nil
|
|
}
|
|
|
|
func (p *Parser) parseJoinConstraint() (JoinConstraint, error) {
|
|
switch p.peek() {
|
|
case ON:
|
|
return p.parseOnConstraint()
|
|
case USING:
|
|
return p.parseUsingConstraint()
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseOnConstraint() (_ *OnConstraint, err error) {
|
|
assert(p.peek() == ON)
|
|
|
|
var con OnConstraint
|
|
con.On, _, _ = p.scan()
|
|
if con.X, err = p.ParseExpr(); err != nil {
|
|
return &con, err
|
|
}
|
|
return &con, nil
|
|
}
|
|
|
|
func (p *Parser) parseUsingConstraint() (*UsingConstraint, error) {
|
|
assert(p.peek() == USING)
|
|
|
|
var con UsingConstraint
|
|
con.Using, _, _ = p.scan()
|
|
|
|
if p.peek() != LP {
|
|
return &con, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
con.Lparen, _, _ = p.scan()
|
|
|
|
for {
|
|
col, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &con, err
|
|
}
|
|
con.Columns = append(con.Columns, col)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &con, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
con.Rparen, _, _ = p.scan()
|
|
|
|
return &con, nil
|
|
}
|
|
|
|
func (p *Parser) parseParenSource() (_ *ParenSource, err error) {
|
|
assert(p.peek() == LP)
|
|
|
|
var source ParenSource
|
|
source.Lparen, _, _ = p.scan()
|
|
|
|
if p.peek() == SELECT {
|
|
if source.X, err = p.parseSelectStatement(false, nil); err != nil {
|
|
return &source, err
|
|
}
|
|
} else {
|
|
if source.X, err = p.parseSource(); err != nil {
|
|
return &source, err
|
|
}
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return nil, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
source.Rparen, _, _ = p.scan()
|
|
|
|
// Only parse aliases for nested select statements.
|
|
if _, ok := source.X.(*SelectStatement); ok && (p.peek() == AS || isIdentToken(p.peek())) {
|
|
if p.peek() == AS {
|
|
source.As, _, _ = p.scan()
|
|
}
|
|
if source.Alias, err = p.parseIdent("table alias"); err != nil {
|
|
return &source, err
|
|
}
|
|
}
|
|
|
|
return &source, nil
|
|
}
|
|
|
|
func (p *Parser) parseQualifiedTableName() (_ *QualifiedTableName, err error) {
|
|
var tbl QualifiedTableName
|
|
|
|
if !isIdentToken(p.peek()) {
|
|
return &tbl, p.errorExpected(p.pos, p.tok, "table name")
|
|
}
|
|
tbl.Name, _ = p.parseIdent("table name")
|
|
|
|
// Parse optional table alias ("AS alias" or just "alias").
|
|
if tok := p.peek(); tok == AS || isIdentToken(tok) {
|
|
if p.peek() == AS {
|
|
tbl.As, _, _ = p.scan()
|
|
}
|
|
if tbl.Alias, err = p.parseIdent("table alias"); err != nil {
|
|
return &tbl, err
|
|
}
|
|
}
|
|
|
|
// Parse optional "INDEXED BY index-name" or "NOT INDEXED".
|
|
/*switch p.peek() {
|
|
case INDEXED:
|
|
tbl.Indexed, _, _ = p.scan()
|
|
if p.peek() != BY {
|
|
return &tbl, p.errorExpected(p.pos, p.tok, "BY")
|
|
}
|
|
tbl.IndexedBy, _, _ = p.scan()
|
|
|
|
if tbl.Index, err = p.parseIdent("index name"); err != nil {
|
|
return &tbl, err
|
|
}
|
|
case NOT:
|
|
tbl.Not, _, _ = p.scan()
|
|
if p.peek() != INDEXED {
|
|
return &tbl, p.errorExpected(p.pos, p.tok, "INDEXED")
|
|
}
|
|
tbl.NotIndexed, _, _ = p.scan()
|
|
}*/
|
|
|
|
return &tbl, nil
|
|
}
|
|
|
|
/*func (p *Parser) parseWithClause() (*WithClause, error) {
|
|
assert(p.peek() == WITH)
|
|
|
|
var clause WithClause
|
|
clause.With, _, _ = p.scan()
|
|
if p.peek() == RECURSIVE {
|
|
clause.Recursive, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse comma-delimited list of common table expressions (CTE).
|
|
for {
|
|
cte, err := p.parseCTE()
|
|
if err != nil {
|
|
return &clause, err
|
|
}
|
|
clause.CTEs = append(clause.CTEs, cte)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
return &clause, nil
|
|
}*/
|
|
|
|
/*func (p *Parser) parseCTE() (_ *CTE, err error) {
|
|
var cte CTE
|
|
if cte.TableName, err = p.parseIdent("table name"); err != nil {
|
|
return &cte, err
|
|
}
|
|
|
|
// Parse optional column list.
|
|
if p.peek() == LP {
|
|
cte.ColumnsLparen, _, _ = p.scan()
|
|
|
|
for {
|
|
column, err := p.parseIdent("column name")
|
|
if err != nil {
|
|
return &cte, err
|
|
}
|
|
cte.Columns = append(cte.Columns, column)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return nil, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
cte.ColumnsRparen, _, _ = p.scan()
|
|
}
|
|
|
|
if p.peek() != AS {
|
|
return nil, p.errorExpected(p.pos, p.tok, "AS")
|
|
}
|
|
cte.As, _, _ = p.scan()
|
|
|
|
// Parse select statement.
|
|
if p.peek() != LP {
|
|
return nil, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
cte.SelectLparen, _, _ = p.scan()
|
|
|
|
if cte.Select, err = p.parseSelectStatement(false, nil); err != nil {
|
|
return &cte, err
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return nil, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
cte.SelectRparen, _, _ = p.scan()
|
|
|
|
return &cte, nil
|
|
}*/
|
|
|
|
func (p *Parser) mustParseLiteral() Expr {
|
|
assert(isLiteralToken(p.tok))
|
|
pos, tok, lit := p.scan()
|
|
switch tok {
|
|
case STRING:
|
|
return &StringLit{ValuePos: pos, Value: lit}
|
|
case INTEGER:
|
|
return &IntegerLit{ValuePos: pos, Value: lit}
|
|
case FLOAT:
|
|
return &FloatLit{ValuePos: pos, Value: lit}
|
|
case TRUE, FALSE:
|
|
return &BoolLit{ValuePos: pos, Value: tok == TRUE}
|
|
default:
|
|
assert(tok == NULL)
|
|
return &NullLit{ValuePos: pos}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) ParseExpr() (expr Expr, err error) {
|
|
return p.parseBinaryExpr(LowestPrec + 1)
|
|
}
|
|
|
|
func (p *Parser) parseOperand() (expr Expr, err error) {
|
|
pos, tok, lit := p.scan()
|
|
switch tok {
|
|
case IDENT, QIDENT:
|
|
ident := &Ident{Name: lit, NamePos: pos, Quoted: tok == QIDENT}
|
|
if p.peek() == DOT {
|
|
return p.parseQualifiedRef(ident)
|
|
} else if p.peek() == LP {
|
|
return p.parseCall(ident)
|
|
}
|
|
return ident, nil
|
|
case MIN, MAX:
|
|
ident := &Ident{Name: lit, NamePos: pos, Quoted: tok == QIDENT}
|
|
return p.parseCall(ident)
|
|
case STRING:
|
|
return &StringLit{ValuePos: pos, Value: lit}, nil
|
|
case FLOAT:
|
|
return &FloatLit{ValuePos: pos, Value: lit}, nil
|
|
case INTEGER:
|
|
return &IntegerLit{ValuePos: pos, Value: lit}, nil
|
|
case NULL:
|
|
return &NullLit{ValuePos: pos}, nil
|
|
case CURRENT_DATE:
|
|
now := time.Now().UTC()
|
|
nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
return &DateLit{ValuePos: pos, Value: nowDate}, nil
|
|
case CURRENT_TIMESTAMP:
|
|
now := time.Now().UTC()
|
|
return &DateLit{ValuePos: pos, Value: now}, nil
|
|
case TRUE, FALSE:
|
|
return &BoolLit{ValuePos: pos, Value: tok == TRUE}, nil
|
|
case PLUS, MINUS, BITNOT:
|
|
expr, err = p.parseOperand()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &UnaryExpr{OpPos: pos, Op: tok, X: expr}, nil
|
|
case LP:
|
|
p.unscan()
|
|
return p.parseParenExpr()
|
|
case LB:
|
|
p.unscan()
|
|
return p.parseSetLiteralExpr()
|
|
case CAST:
|
|
p.unscan()
|
|
return p.parseCastExpr()
|
|
case CASE:
|
|
p.unscan()
|
|
return p.parseCaseExpr()
|
|
case NOT, EXISTS:
|
|
p.unscan()
|
|
return p.parseExists()
|
|
case SELECT:
|
|
p.unscan()
|
|
return p.parseSelectStatement(false, nil)
|
|
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, "expression")
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseBinaryExpr(prec1 int) (expr Expr, err error) {
|
|
x, err := p.parseOperand()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
if p.peek().Precedence() < prec1 {
|
|
return x, nil
|
|
}
|
|
|
|
pos, op, err := p.scanBinaryOp()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch op {
|
|
case IN, NOTIN:
|
|
y, err := p.parseExprList()
|
|
if err != nil {
|
|
return x, err
|
|
}
|
|
x = &BinaryExpr{X: x, OpPos: pos, Op: op, Y: y}
|
|
|
|
case BETWEEN, NOTBETWEEN:
|
|
// Parsing the expression should yield a binary expression with AND op.
|
|
// However, we don't want to conflate the boolean AND and the ranged AND
|
|
// so we convert the expression to a Range.
|
|
if rng, err := p.parseBinaryExpr(LowestPrec + 1); err != nil {
|
|
return x, err
|
|
} else if rng, ok := rng.(*BinaryExpr); !ok || rng.Op != AND {
|
|
return x, p.errorExpected(p.pos, p.tok, "range expression")
|
|
} else {
|
|
x = &BinaryExpr{
|
|
X: x,
|
|
OpPos: pos,
|
|
Op: op,
|
|
Y: &Range{X: rng.X, And: rng.OpPos, Y: rng.Y},
|
|
}
|
|
}
|
|
|
|
default:
|
|
y, err := p.parseBinaryExpr(op.Precedence() + 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
x = &BinaryExpr{X: x, OpPos: pos, Op: op, Y: y}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func (p *Parser) parseExprList() (_ *ExprList, err error) {
|
|
var list ExprList
|
|
if p.peek() != LP {
|
|
return &list, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
list.Lparen, _, _ = p.scan()
|
|
|
|
for p.peek() != RP {
|
|
x, err := p.ParseExpr()
|
|
if err != nil {
|
|
return &list, err
|
|
}
|
|
list.Exprs = append(list.Exprs, x)
|
|
|
|
if p.peek() == RP {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &list, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
p.scan()
|
|
}
|
|
|
|
list.Rparen, _, _ = p.scan()
|
|
|
|
return &list, nil
|
|
}
|
|
|
|
func (p *Parser) parseQualifiedRef(table *Ident) (_ *QualifiedRef, err error) {
|
|
assert(p.peek() == DOT)
|
|
|
|
var expr QualifiedRef
|
|
expr.Table = table
|
|
expr.Dot, _, _ = p.scan()
|
|
|
|
if p.peek() == STAR {
|
|
expr.Star, _, _ = p.scan()
|
|
} else if isIdentToken(p.peek()) {
|
|
pos, tok, lit := p.scan()
|
|
expr.Column = &Ident{Name: lit, NamePos: pos, Quoted: tok == QIDENT}
|
|
} else {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "column name")
|
|
}
|
|
|
|
return &expr, nil
|
|
}
|
|
|
|
func (p *Parser) parseCall(name *Ident) (_ *Call, err error) {
|
|
assert(p.peek() == LP)
|
|
|
|
var expr Call
|
|
expr.Name = name
|
|
expr.Lparen, _, _ = p.scan()
|
|
|
|
// Parse argument list: either "*" or "[DISTINCT] expr, expr..."
|
|
if p.peek() == STAR {
|
|
expr.Star, _, _ = p.scan()
|
|
} else {
|
|
if p.peek() == DISTINCT {
|
|
expr.Distinct, _, _ = p.scan()
|
|
}
|
|
for p.peek() != RP {
|
|
arg, err := p.ParseExpr()
|
|
if err != nil {
|
|
return &expr, err
|
|
}
|
|
expr.Args = append(expr.Args, arg)
|
|
|
|
if tok := p.peek(); tok == COMMA {
|
|
p.scan()
|
|
} else if tok != RP {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "comma or right paren")
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
expr.Rparen, _, _ = p.scan()
|
|
|
|
// Parse optional filter clause.
|
|
if p.peek() == FILTER {
|
|
if expr.Filter, err = p.parseFilterClause(); err != nil {
|
|
return &expr, err
|
|
}
|
|
}
|
|
|
|
// Parse optional over clause.
|
|
if p.peek() == OVER {
|
|
if expr.Over, err = p.parseOverClause(); err != nil {
|
|
return &expr, err
|
|
}
|
|
}
|
|
|
|
return &expr, nil
|
|
}
|
|
|
|
func (p *Parser) parseFilterClause() (_ *FilterClause, err error) {
|
|
assert(p.peek() == FILTER)
|
|
|
|
var clause FilterClause
|
|
clause.Filter, _, _ = p.scan()
|
|
|
|
if p.peek() != LP {
|
|
return &clause, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
clause.Lparen, _, _ = p.scan()
|
|
|
|
if p.peek() != WHERE {
|
|
return &clause, p.errorExpected(p.pos, p.tok, "WHERE")
|
|
}
|
|
clause.Where, _, _ = p.scan()
|
|
|
|
if clause.X, err = p.ParseExpr(); err != nil {
|
|
return &clause, err
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return &clause, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
clause.Rparen, _, _ = p.scan()
|
|
|
|
return &clause, nil
|
|
}
|
|
|
|
func (p *Parser) parseOverClause() (_ *OverClause, err error) {
|
|
assert(p.peek() == OVER)
|
|
|
|
var clause OverClause
|
|
clause.Over, _, _ = p.scan()
|
|
|
|
// If specifying a window name, read it and exit.
|
|
if isIdentToken(p.peek()) {
|
|
pos, tok, lit := p.scan()
|
|
clause.Name = &Ident{Name: lit, NamePos: pos, Quoted: tok == QIDENT}
|
|
return &clause, nil
|
|
}
|
|
|
|
if clause.Definition, err = p.parseWindowDefinition(); err != nil {
|
|
return &clause, err
|
|
}
|
|
return &clause, nil
|
|
}
|
|
|
|
func (p *Parser) parseWindowDefinition() (_ *WindowDefinition, err error) {
|
|
var def WindowDefinition
|
|
|
|
// Otherwise parse the window definition.
|
|
if p.peek() != LP {
|
|
return &def, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
def.Lparen, _, _ = p.scan()
|
|
|
|
// Read base window name.
|
|
if isIdentToken(p.peek()) {
|
|
pos, tok, lit := p.scan()
|
|
def.Base = &Ident{Name: lit, NamePos: pos, Quoted: tok == QIDENT}
|
|
}
|
|
|
|
// Parse "PARTITION BY expr, expr..."
|
|
if p.peek() == PARTITION {
|
|
def.Partition, _, _ = p.scan()
|
|
if p.peek() != BY {
|
|
return &def, p.errorExpected(p.pos, p.tok, "BY")
|
|
}
|
|
def.PartitionBy, _, _ = p.scan()
|
|
|
|
for {
|
|
partition, err := p.ParseExpr()
|
|
if err != nil {
|
|
return &def, err
|
|
}
|
|
def.Partitions = append(def.Partitions, partition)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
}
|
|
|
|
// Parse "ORDER BY ordering-term, ordering-term..."
|
|
if p.peek() == ORDER {
|
|
def.Order, _, _ = p.scan()
|
|
if p.peek() != BY {
|
|
return &def, p.errorExpected(p.pos, p.tok, "BY")
|
|
}
|
|
def.OrderBy, _, _ = p.scan()
|
|
|
|
for {
|
|
term, err := p.parseOrderingTerm()
|
|
if err != nil {
|
|
return &def, err
|
|
}
|
|
def.OrderingTerms = append(def.OrderingTerms, term)
|
|
|
|
if p.peek() != COMMA {
|
|
break
|
|
}
|
|
p.scan()
|
|
}
|
|
}
|
|
|
|
// Parse frame spec.
|
|
if tok := p.peek(); tok == RANGE || tok == ROWS || tok == GROUPS {
|
|
if def.Frame, err = p.parseFrameSpec(); err != nil {
|
|
return &def, err
|
|
}
|
|
}
|
|
|
|
// Parse final rparen.
|
|
if p.peek() != RP {
|
|
return &def, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
def.Rparen, _, _ = p.scan()
|
|
|
|
return &def, nil
|
|
}
|
|
|
|
func (p *Parser) parseOrderingTerm() (_ *OrderingTerm, err error) {
|
|
var term OrderingTerm
|
|
if term.X, err = p.ParseExpr(); err != nil {
|
|
return &term, err
|
|
}
|
|
|
|
// Parse optional sort direction ("ASC" or "DESC")
|
|
switch p.peek() {
|
|
case ASC:
|
|
term.Asc, _, _ = p.scan()
|
|
case DESC:
|
|
term.Desc, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse optional "NULLS FIRST" or "NULLS LAST"
|
|
if p.peek() == NULLS {
|
|
term.Nulls, _, _ = p.scan()
|
|
switch p.peek() {
|
|
case FIRST:
|
|
term.NullsFirst, _, _ = p.scan()
|
|
case LAST:
|
|
term.NullsLast, _, _ = p.scan()
|
|
default:
|
|
return &term, p.errorExpected(p.pos, p.tok, "FIRST or LAST")
|
|
}
|
|
}
|
|
|
|
return &term, nil
|
|
}
|
|
|
|
func (p *Parser) parseFrameSpec() (_ *FrameSpec, err error) {
|
|
assert(p.peek() == RANGE || p.peek() == ROWS || p.peek() == GROUPS)
|
|
|
|
var spec FrameSpec
|
|
|
|
switch p.peek() {
|
|
case RANGE:
|
|
spec.Range, _, _ = p.scan()
|
|
case ROWS:
|
|
spec.Rows, _, _ = p.scan()
|
|
case GROUPS:
|
|
spec.Groups, _, _ = p.scan()
|
|
}
|
|
|
|
// Parsing BETWEEN indicates that two expressions are required.
|
|
if p.peek() == BETWEEN {
|
|
spec.Between, _, _ = p.scan()
|
|
}
|
|
|
|
// Parse X expression: "UNBOUNDED PRECEDING", "CURRENT ROW", "expr PRECEDING|FOLLOWING"
|
|
if p.peek() == UNBOUNDED {
|
|
spec.UnboundedX, _, _ = p.scan()
|
|
if p.peek() != PRECEDING {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "PRECEDING")
|
|
}
|
|
spec.PrecedingX, _, _ = p.scan()
|
|
} else if p.peek() == CURRENT {
|
|
spec.CurrentX, _, _ = p.scan()
|
|
if p.peek() != ROW {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "ROW")
|
|
}
|
|
spec.CurrentRowX, _, _ = p.scan()
|
|
} else {
|
|
if spec.X, err = p.ParseExpr(); err != nil {
|
|
return &spec, err
|
|
}
|
|
if p.peek() == PRECEDING {
|
|
spec.PrecedingX, _, _ = p.scan()
|
|
} else if p.peek() == FOLLOWING && spec.Between.IsValid() { // FOLLOWING only allowed with BETWEEN
|
|
spec.FollowingX, _, _ = p.scan()
|
|
} else {
|
|
if spec.Between.IsValid() {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "PRECEDING or FOLLOWING")
|
|
}
|
|
return &spec, p.errorExpected(p.pos, p.tok, "PRECEDING")
|
|
}
|
|
}
|
|
|
|
// Read "AND y" if range is BETWEEN.
|
|
if spec.Between.IsValid() {
|
|
if p.peek() != AND {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "AND")
|
|
}
|
|
spec.And, _, _ = p.scan()
|
|
|
|
// Parse Y expression: "UNBOUNDED FOLLOWING", "CURRENT ROW", "expr PRECEDING|FOLLOWING"
|
|
if p.peek() == UNBOUNDED {
|
|
spec.UnboundedY, _, _ = p.scan()
|
|
if p.peek() != FOLLOWING {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "FOLLOWING")
|
|
}
|
|
spec.FollowingY, _, _ = p.scan()
|
|
} else if p.peek() == CURRENT {
|
|
spec.CurrentY, _, _ = p.scan()
|
|
if p.peek() != ROW {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "ROW")
|
|
}
|
|
spec.CurrentRowY, _, _ = p.scan()
|
|
} else {
|
|
if spec.Y, err = p.ParseExpr(); err != nil {
|
|
return &spec, err
|
|
}
|
|
if p.peek() == PRECEDING {
|
|
spec.PrecedingY, _, _ = p.scan()
|
|
} else if p.peek() == FOLLOWING {
|
|
spec.FollowingY, _, _ = p.scan()
|
|
} else {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "PRECEDING or FOLLOWING")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse optional EXCLUDE.
|
|
if p.peek() == EXCLUDE {
|
|
spec.Exclude, _, _ = p.scan()
|
|
|
|
switch p.peek() {
|
|
case NO:
|
|
spec.ExcludeNo, _, _ = p.scan()
|
|
if p.peek() != OTHERS {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "OTHERS")
|
|
}
|
|
spec.ExcludeNoOthers, _, _ = p.scan()
|
|
case CURRENT:
|
|
spec.ExcludeCurrent, _, _ = p.scan()
|
|
if p.peek() != ROW {
|
|
return &spec, p.errorExpected(p.pos, p.tok, "ROW")
|
|
}
|
|
spec.ExcludeCurrentRow, _, _ = p.scan()
|
|
case GROUP:
|
|
spec.ExcludeGroup, _, _ = p.scan()
|
|
case TIES:
|
|
spec.ExcludeTies, _, _ = p.scan()
|
|
default:
|
|
return &spec, p.errorExpected(p.pos, p.tok, "NO OTHERS, CURRENT ROW, GROUP, or TIES")
|
|
}
|
|
}
|
|
|
|
return &spec, nil
|
|
}
|
|
|
|
func (p *Parser) parseParenExpr() (_ *ParenExpr, err error) {
|
|
var expr ParenExpr
|
|
expr.Lparen, _, _ = p.scan()
|
|
if expr.X, err = p.ParseExpr(); err != nil {
|
|
return &expr, err
|
|
}
|
|
expr.Rparen, _, _ = p.scan()
|
|
return &expr, nil
|
|
}
|
|
|
|
func (p *Parser) parseSetLiteralExpr() (_ *SetLiteralExpr, err error) {
|
|
var expr SetLiteralExpr
|
|
expr.Lbracket, _, _ = p.scan()
|
|
|
|
for p.peek() != RB {
|
|
x, err := p.ParseExpr()
|
|
if err != nil {
|
|
return &expr, err
|
|
}
|
|
expr.Members = append(expr.Members, x)
|
|
|
|
if p.peek() == RB {
|
|
break
|
|
} else if p.peek() != COMMA {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "comma or right bracket")
|
|
}
|
|
p.scan()
|
|
}
|
|
|
|
expr.Rbracket, _, _ = p.scan()
|
|
return &expr, nil
|
|
}
|
|
|
|
func (p *Parser) parseCastExpr() (_ *CastExpr, err error) {
|
|
assert(p.peek() == CAST)
|
|
|
|
var expr CastExpr
|
|
expr.Cast, _, _ = p.scan()
|
|
|
|
if p.peek() != LP {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
expr.Lparen, _, _ = p.scan()
|
|
|
|
if expr.X, err = p.ParseExpr(); err != nil {
|
|
return &expr, err
|
|
}
|
|
|
|
if p.peek() != AS {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "AS")
|
|
}
|
|
expr.As, _, _ = p.scan()
|
|
|
|
if expr.Type, err = p.parseType(); err != nil {
|
|
return &expr, err
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
expr.Rparen, _, _ = p.scan()
|
|
return &expr, nil
|
|
}
|
|
|
|
func (p *Parser) parseCaseExpr() (_ *CaseExpr, err error) {
|
|
assert(p.peek() == CASE)
|
|
|
|
var expr CaseExpr
|
|
expr.Case, _, _ = p.scan()
|
|
|
|
// Parse optional expression if WHEN is not next.
|
|
if p.peek() != WHEN {
|
|
if expr.Operand, err = p.ParseExpr(); err != nil {
|
|
return &expr, err
|
|
}
|
|
}
|
|
|
|
// Parse one or more WHEN/THEN pairs.
|
|
for {
|
|
var blk CaseBlock
|
|
if p.peek() != WHEN {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "WHEN")
|
|
}
|
|
blk.When, _, _ = p.scan()
|
|
|
|
if blk.Condition, err = p.ParseExpr(); err != nil {
|
|
return &expr, err
|
|
}
|
|
|
|
if p.peek() != THEN {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "THEN")
|
|
}
|
|
blk.Then, _, _ = p.scan()
|
|
|
|
if blk.Body, err = p.ParseExpr(); err != nil {
|
|
return &expr, err
|
|
}
|
|
|
|
expr.Blocks = append(expr.Blocks, &blk)
|
|
|
|
if tok := p.peek(); tok == ELSE || tok == END {
|
|
break
|
|
} else if tok != WHEN {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "WHEN, ELSE or END")
|
|
}
|
|
}
|
|
|
|
// Parse optional ELSE block.
|
|
if p.peek() == ELSE {
|
|
expr.Else, _, _ = p.scan()
|
|
if expr.ElseExpr, err = p.ParseExpr(); err != nil {
|
|
return &expr, err
|
|
}
|
|
}
|
|
|
|
if p.peek() != END {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "END")
|
|
}
|
|
expr.End, _, _ = p.scan()
|
|
|
|
return &expr, nil
|
|
}
|
|
|
|
func (p *Parser) parseExists() (_ *Exists, err error) {
|
|
assert(p.peek() == NOT || p.peek() == EXISTS)
|
|
|
|
var expr Exists
|
|
|
|
if p.peek() == NOT {
|
|
expr.Not, _, _ = p.scan()
|
|
}
|
|
|
|
if p.peek() != EXISTS {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "EXISTS")
|
|
}
|
|
expr.Exists, _, _ = p.scan()
|
|
|
|
if p.peek() != LP {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "left paren")
|
|
}
|
|
expr.Lparen, _, _ = p.scan()
|
|
|
|
if expr.Select, err = p.parseSelectStatement(false, nil); err != nil {
|
|
return &expr, err
|
|
}
|
|
|
|
if p.peek() != RP {
|
|
return &expr, p.errorExpected(p.pos, p.tok, "right paren")
|
|
}
|
|
expr.Rparen, _, _ = p.scan()
|
|
|
|
return &expr, nil
|
|
}
|
|
|
|
func (p *Parser) parseIntegerLiteral(desc string) (*IntegerLit, error) {
|
|
pos, tok, lit := p.scan()
|
|
|
|
switch tok {
|
|
case INTEGER:
|
|
return &IntegerLit{ValuePos: pos, Value: lit}, nil
|
|
default:
|
|
return nil, p.errorExpected(p.pos, p.tok, desc)
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseAlterTableStatement() (_ *AlterTableStatement, err error) {
|
|
assert(p.peek() == ALTER)
|
|
|
|
var stmt AlterTableStatement
|
|
stmt.Alter, _, _ = p.scan()
|
|
if p.peek() != TABLE {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "TABLE")
|
|
}
|
|
stmt.Table, _, _ = p.scan()
|
|
|
|
if stmt.Name, err = p.parseIdent("table name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
switch p.peek() {
|
|
case RENAME:
|
|
stmt.Rename, _, _ = p.scan()
|
|
|
|
/*// Parse "RENAME TO new-table-name".
|
|
if p.peek() == TO {
|
|
stmt.RenameTo, _, _ = p.scan()
|
|
if stmt.NewName, err = p.parseIdent("new table name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
// Otherwise parse "RENAME [COLUMN] column-name TO new-column-name".
|
|
if p.peek() == COLUMN {
|
|
stmt.RenameColumn, _, _ = p.scan()
|
|
} else if !isIdentToken(p.peek()) {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "COLUMN keyword or column name")
|
|
}
|
|
if stmt.OldColumnName, err = p.parseIdent("column name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
if p.peek() != TO {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "TO")
|
|
}
|
|
stmt.To, _, _ = p.scan()
|
|
if stmt.NewColumnName, err = p.parseIdent("new column name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
|
|
return &stmt, nil
|
|
case ADD:
|
|
stmt.Add, _, _ = p.scan()
|
|
if p.peek() == COLUMN {
|
|
stmt.AddColumn, _, _ = p.scan()
|
|
} else if !isIdentToken(p.peek()) {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "COLUMN keyword or column name")
|
|
}
|
|
if stmt.ColumnDef, err = p.parseColumnDefinition(); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
|
|
case DROP:
|
|
stmt.Drop, _, _ = p.scan()
|
|
if p.peek() == COLUMN {
|
|
stmt.DropColumn, _, _ = p.scan()
|
|
} else if !isIdentToken(p.peek()) {
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "COLUMN keyword or column name")
|
|
}
|
|
if stmt.DropColumnName, err = p.parseIdent("column name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
|
|
default:
|
|
return &stmt, p.errorExpected(p.pos, p.tok, "ADD, DROP or RENAME")
|
|
}
|
|
}
|
|
|
|
/*func (p *Parser) parseAnalyzeStatement() (_ *AnalyzeStatement, err error) {
|
|
assert(p.peek() == ANALYZE)
|
|
|
|
var stmt AnalyzeStatement
|
|
stmt.Analyze, _, _ = p.scan()
|
|
if stmt.Name, err = p.parseIdent("table or index name"); err != nil {
|
|
return &stmt, err
|
|
}
|
|
return &stmt, nil
|
|
}*/
|
|
|
|
func (p *Parser) scan() (Pos, Token, string) {
|
|
if p.full {
|
|
p.full = false
|
|
return p.pos, p.tok, p.lit
|
|
}
|
|
|
|
p.pos, p.tok, p.lit = p.s.Scan()
|
|
return p.pos, p.tok, p.lit
|
|
}
|
|
|
|
// scanBinaryOp performs a scan but combines multi-word operations into a single token.
|
|
func (p *Parser) scanBinaryOp() (Pos, Token, error) {
|
|
pos, tok, _ := p.scan()
|
|
switch tok {
|
|
case IS:
|
|
switch p.peek() {
|
|
case NOT:
|
|
p.scan()
|
|
return pos, ISNOT, nil
|
|
}
|
|
return pos, IS, nil
|
|
case NOT:
|
|
switch p.peek() {
|
|
case IN:
|
|
p.scan()
|
|
return pos, NOTIN, nil
|
|
case LIKE:
|
|
p.scan()
|
|
return pos, NOTLIKE, nil
|
|
case GLOB:
|
|
p.scan()
|
|
return pos, NOTGLOB, nil
|
|
case REGEXP:
|
|
p.scan()
|
|
return pos, NOTREGEXP, nil
|
|
case MATCH:
|
|
p.scan()
|
|
return pos, NOTMATCH, nil
|
|
case BETWEEN:
|
|
p.scan()
|
|
return pos, NOTBETWEEN, nil
|
|
default:
|
|
return pos, tok, p.errorExpected(p.pos, p.tok, "IN, LIKE, GLOB, REGEXP, MATCH, or BETWEEN")
|
|
}
|
|
default:
|
|
return pos, tok, nil
|
|
}
|
|
}
|
|
|
|
func (p *Parser) peek() Token {
|
|
if !p.full {
|
|
p.scan()
|
|
p.unscan()
|
|
}
|
|
return p.tok
|
|
}
|
|
|
|
func (p *Parser) unscan() {
|
|
assert(!p.full)
|
|
p.full = true
|
|
}
|
|
|
|
func (p *Parser) errorExpected(pos Pos, tok Token, msg string) error {
|
|
msg = "expected " + msg
|
|
if pos == p.pos {
|
|
if p.tok.IsLiteral() {
|
|
msg += ", found " + p.lit
|
|
} else {
|
|
msg += ", found '" + p.tok.String() + "'"
|
|
}
|
|
}
|
|
return &Error{Pos: pos, Msg: msg}
|
|
}
|
|
|
|
// Error represents a parse error.
|
|
type Error struct {
|
|
Pos Pos
|
|
Msg string
|
|
}
|
|
|
|
// Error implements the error interface.
|
|
func (e Error) Error() string {
|
|
if e.Pos.IsValid() {
|
|
return e.Pos.String() + ": " + e.Msg
|
|
}
|
|
return e.Msg
|
|
}
|
|
|
|
// isTableOptionStartToken returns true if tok is the initial token of a table option.
|
|
func isTableOptionStartToken(tok Token) bool {
|
|
switch tok {
|
|
case KEYPARTITIONS, SHARDWIDTH:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// isConstraintStartToken returns true if tok is the initial token of a constraint.
|
|
func isConstraintStartToken(tok Token, isTable bool) bool {
|
|
switch tok {
|
|
//case CONSTRAINT, PRIMARY, UNIQUE, CHECK:
|
|
// return true // table & column
|
|
//case FOREIGN:
|
|
// return isTable // table only
|
|
case MIN, MAX, TIMEUNIT, TIMEQUANTUM, CACHETYPE:
|
|
return !isTable // column only
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// isLiteralToken returns true if token represents a literal value.
|
|
func isLiteralToken(tok Token) bool {
|
|
switch tok {
|
|
case FLOAT, INTEGER, STRING, BLOB, TRUE, FALSE, NULL,
|
|
CURRENT_DATE, CURRENT_TIMESTAMP:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|