mirror of
https://github.com/chaisql/chai.git
synced 2025-11-01 03:12:38 +08:00
Previously, expressions and params were evaluated during the planning phase. This change builds the query plan without evaluating params and expressions which are then evaluated only during the execution phase.
239 lines
5.6 KiB
Go
239 lines
5.6 KiB
Go
package parser
|
|
|
|
import (
|
|
"github.com/genjidb/genji/internal/database"
|
|
"github.com/genjidb/genji/internal/expr"
|
|
"github.com/genjidb/genji/internal/query/statement"
|
|
"github.com/genjidb/genji/internal/sql/scanner"
|
|
"github.com/genjidb/genji/internal/stringutil"
|
|
)
|
|
|
|
// parseInsertStatement parses an insert string and returns a Statement AST object.
|
|
// This function assumes the INSERT token has already been consumed.
|
|
func (p *Parser) parseInsertStatement() (*statement.StreamStmt, error) {
|
|
var stmt statement.InsertStmt
|
|
var err error
|
|
|
|
// Parse "INTO".
|
|
if err := p.parseTokens(scanner.INTO); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse table name
|
|
stmt.TableName, err = p.parseIdent()
|
|
if err != nil {
|
|
pErr := err.(*ParseError)
|
|
pErr.Expected = []string{"table_name"}
|
|
return nil, pErr
|
|
}
|
|
|
|
// Parse path list: (a, b, c)
|
|
stmt.Fields, err = p.parseFieldList()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check if VALUES or SELECT token exists.
|
|
tok, pos, lit := p.ScanIgnoreWhitespace()
|
|
switch tok {
|
|
case scanner.VALUES:
|
|
// Parse VALUES (v1, v2, v3)
|
|
stmt.Values, err = p.parseValues(stmt.Fields)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case scanner.SELECT:
|
|
stmt.SelectStmt, err = p.parseSelectStatement()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, newParseError(scanner.Tokstr(tok, lit), []string{"VALUES", "SELECT"}, pos)
|
|
}
|
|
|
|
// Parse ON CONFLICT clause
|
|
stmt.OnConflict, err = p.parseOnConflictClause()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stmt.Returning, err = p.parseReturning()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return stmt.ToStream()
|
|
}
|
|
|
|
// parseFieldList parses a list of fields in the form: (path, path, ...), if exists.
|
|
// If the list is empty, it returns an error.
|
|
func (p *Parser) parseFieldList() ([]string, error) {
|
|
// Parse ( token.
|
|
if ok, err := p.parseOptional(scanner.LPAREN); !ok || err != nil {
|
|
p.Unscan()
|
|
return nil, err
|
|
}
|
|
|
|
// Parse path list.
|
|
var fields []string
|
|
var err error
|
|
if fields, err = p.parseIdentList(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse required ) token.
|
|
if err := p.parseTokens(scanner.RPAREN); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return fields, nil
|
|
}
|
|
|
|
// parseValues parses the "VALUES" clause of the query, if it exists.
|
|
func (p *Parser) parseValues(fields []string) ([]expr.Expr, error) {
|
|
if len(fields) > 0 {
|
|
return p.parseDocumentsWithFields(fields)
|
|
}
|
|
|
|
return p.parseLiteralDocOrParamList()
|
|
}
|
|
|
|
// parseExprListValues parses the "VALUES" clause of the query, if it exists.
|
|
func (p *Parser) parseDocumentsWithFields(fields []string) ([]expr.Expr, error) {
|
|
var docs []expr.Expr
|
|
|
|
// Parse first (required) value list.
|
|
doc, err := p.parseExprListWithFields(fields)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
docs = append(docs, doc)
|
|
|
|
// Parse remaining (optional) values.
|
|
for {
|
|
if tok, _, _ := p.ScanIgnoreWhitespace(); tok != scanner.COMMA {
|
|
p.Unscan()
|
|
break
|
|
}
|
|
|
|
doc, err := p.parseExprListWithFields(fields)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
docs = append(docs, doc)
|
|
}
|
|
|
|
return docs, nil
|
|
}
|
|
|
|
// parseParamOrDocument parses either a parameter or a document.
|
|
func (p *Parser) parseExprListWithFields(fields []string) (*expr.KVPairs, error) {
|
|
list, err := p.parseExprList(scanner.LPAREN, scanner.RPAREN)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var pairs expr.KVPairs
|
|
pairs.Pairs = make([]expr.KVPair, len(list))
|
|
|
|
if len(fields) != len(list) {
|
|
return nil, stringutil.Errorf("%d values for %d fields", len(list), len(fields))
|
|
}
|
|
|
|
for i := range list {
|
|
pairs.Pairs[i].K = fields[i]
|
|
pairs.Pairs[i].V = list[i]
|
|
}
|
|
|
|
return &pairs, nil
|
|
}
|
|
|
|
// parseExprListValues parses the "VALUES" clause of the query, if it exists.
|
|
func (p *Parser) parseLiteralDocOrParamList() ([]expr.Expr, error) {
|
|
var docs []expr.Expr
|
|
|
|
// Parse first (required) value list.
|
|
doc, err := p.parseParamOrDocument()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
docs = append(docs, doc)
|
|
|
|
// Parse remaining (optional) values.
|
|
for {
|
|
if tok, _, _ := p.ScanIgnoreWhitespace(); tok != scanner.COMMA {
|
|
p.Unscan()
|
|
break
|
|
}
|
|
|
|
doc, err := p.parseParamOrDocument()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
docs = append(docs, doc)
|
|
}
|
|
|
|
return docs, nil
|
|
}
|
|
|
|
// parseParamOrDocument parses either a parameter or a document.
|
|
func (p *Parser) parseParamOrDocument() (expr.Expr, error) {
|
|
// Parse a param first
|
|
prm, err := p.parseParam()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if prm != nil {
|
|
return prm, nil
|
|
}
|
|
|
|
// If not a param, start over
|
|
p.Unscan()
|
|
|
|
// Expect a document
|
|
return p.ParseDocument()
|
|
}
|
|
|
|
func (p *Parser) parseOnConflictClause() (database.OnInsertConflictAction, error) {
|
|
// Parse ON CONFLICT DO clause: ON CONFLICT DO action
|
|
if ok, err := p.parseOptional(scanner.ON, scanner.CONFLICT); !ok || err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tok, pos, lit := p.ScanIgnoreWhitespace()
|
|
// SQLite compatibility: ON CONFLICT [IGNORE | REPLACE]
|
|
switch tok {
|
|
case scanner.IGNORE:
|
|
return database.OnInsertConflictDoNothing, nil
|
|
case scanner.REPLACE:
|
|
return database.OnInsertConflictDoReplace, nil
|
|
}
|
|
|
|
// DO [NOTHING | REPLACE]
|
|
if tok != scanner.DO {
|
|
return nil, newParseError(scanner.Tokstr(tok, lit), []string{scanner.DO.String()}, pos)
|
|
}
|
|
|
|
tok, pos, lit = p.ScanIgnoreWhitespace()
|
|
switch tok {
|
|
case scanner.NOTHING:
|
|
return database.OnInsertConflictDoNothing, nil
|
|
case scanner.REPLACE:
|
|
return database.OnInsertConflictDoReplace, nil
|
|
}
|
|
return nil, newParseError(scanner.Tokstr(tok, lit), []string{scanner.NOTHING.String(), scanner.REPLACE.String()}, pos)
|
|
}
|
|
|
|
func (p *Parser) parseReturning() ([]expr.Expr, error) {
|
|
// Parse RETURNING clause: RETURNING expr [AS alias]
|
|
if ok, err := p.parseOptional(scanner.RETURNING); !ok || err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return p.parseProjectedExprs()
|
|
}
|