Files
chaisql/internal/sql/parser/parser.go
Jean Hadrien Chabran 4a6e68439a Refactor to handle errors with internal/errors (#432)
All new error handling code now rely on internal/errors package
which provides a compilation time toggle that enables to capture
stacktraces for easier debugging while developing.

It also comes with a new testutil/assert package which replaces the require
package when it comes to checking or comparing errors and printing the
stack traces if needed.

Finally, the test target of the Makefile uses the debug build tag by default. 
A testnodebug target is also provided for convenience and to make sure no
tests are broken due to not having used the internal/errors or testutil/assert package.

See #431 for more details
2021-08-22 11:47:54 +03:00

250 lines
6.5 KiB
Go

package parser
import (
"io"
"strings"
"github.com/genjidb/genji/document"
"github.com/genjidb/genji/internal/errors"
"github.com/genjidb/genji/internal/expr"
"github.com/genjidb/genji/internal/expr/functions"
"github.com/genjidb/genji/internal/query"
"github.com/genjidb/genji/internal/query/statement"
"github.com/genjidb/genji/internal/sql/scanner"
"github.com/genjidb/genji/internal/stringutil"
)
// Parser represents an Genji SQL Parser.
type Parser struct {
s *scanner.Scanner
orderedParams int
namedParams int
packagesTable functions.Packages
}
// NewParser returns a new instance of Parser.
func NewParser(r io.Reader) *Parser {
return NewParserWithOptions(r, nil)
}
// NewParserWithOptions returns a new instance of Parser using given Options.
func NewParserWithOptions(r io.Reader, opts *Options) *Parser {
if opts == nil {
opts = defaultOptions()
}
return &Parser{s: scanner.NewScanner(r), packagesTable: opts.Packages}
}
// ParseQuery parses a query string and returns its AST representation.
func ParseQuery(s string) (query.Query, error) {
return NewParser(strings.NewReader(s)).ParseQuery()
}
// ParsePath parses a path to a value in a document.
func ParsePath(s string) (document.Path, error) {
return NewParser(strings.NewReader(s)).parsePath()
}
// ParseExpr parses an expression.
func ParseExpr(s string) (expr.Expr, error) {
e, err := NewParser(strings.NewReader(s)).ParseExpr()
return e, err
}
// MustParseExpr calls ParseExpr and panics if it returns an error.
func MustParseExpr(s string) expr.Expr {
e, err := ParseExpr(s)
if err != nil {
panic(err)
}
return e
}
// ParseQuery parses a Genji SQL string and returns a Query.
func (p *Parser) ParseQuery() (query.Query, error) {
var statements []statement.Statement
semi := true
for {
if tok, pos, lit := p.ScanIgnoreWhitespace(); tok == scanner.EOF {
return query.New(statements...), nil
} else if tok == scanner.SEMICOLON {
semi = true
} else {
if !semi {
return query.Query{}, newParseError(scanner.Tokstr(tok, lit), []string{";"}, pos)
}
p.Unscan()
s, err := p.ParseStatement()
if err != nil {
return query.Query{}, err
}
statements = append(statements, s)
semi = false
}
}
}
// ParseStatement parses a Genji SQL string and returns a Statement AST object.
func (p *Parser) ParseStatement() (statement.Statement, error) {
tok, pos, lit := p.ScanIgnoreWhitespace()
switch tok {
case scanner.ALTER:
return p.parseAlterStatement()
case scanner.BEGIN:
return p.parseBeginStatement()
case scanner.COMMIT:
return p.parseCommitStatement()
case scanner.SELECT:
return p.parseSelectStatement()
case scanner.DELETE:
return p.parseDeleteStatement()
case scanner.UPDATE:
return p.parseUpdateStatement()
case scanner.INSERT:
return p.parseInsertStatement()
case scanner.CREATE:
return p.parseCreateStatement()
case scanner.DROP:
return p.parseDropStatement()
case scanner.EXPLAIN:
return p.parseExplainStatement()
case scanner.REINDEX:
return p.parseReIndexStatement()
case scanner.ROLLBACK:
return p.parseRollbackStatement()
}
return nil, newParseError(scanner.Tokstr(tok, lit), []string{
"ALTER", "BEGIN", "COMMIT", "SELECT", "DELETE", "UPDATE", "INSERT", "CREATE", "DROP", "EXPLAIN", "REINDEX", "ROLLBACK",
}, pos)
}
// parseCondition parses the "WHERE" clause of the query, if it exists.
func (p *Parser) parseCondition() (expr.Expr, error) {
// Check if the WHERE token exists.
if ok, err := p.parseOptional(scanner.WHERE); !ok || err != nil {
return nil, err
}
// Scan the identifier for the source.
expr, err := p.ParseExpr()
if err != nil {
return nil, err
}
return expr, nil
}
// parsePathList parses a list of paths in the form: (path, path, ...), if exists
func (p *Parser) parsePathList() ([]document.Path, error) {
// Parse ( token.
if ok, err := p.parseOptional(scanner.LPAREN); !ok || err != nil {
return nil, err
}
var paths []document.Path
var err error
var path document.Path
// Parse first (required) path.
if path, err = p.parsePath(); err != nil {
return nil, err
}
paths = append(paths, path)
// Parse remaining (optional) paths.
for {
if tok, _, _ := p.ScanIgnoreWhitespace(); tok != scanner.COMMA {
p.Unscan()
break
}
vp, err := p.parsePath()
if err != nil {
return nil, err
}
paths = append(paths, vp)
}
// Parse required ) token.
if err := p.parseTokens(scanner.RPAREN); err != nil {
return nil, err
}
return paths, nil
}
// Scan returns the next token from the underlying scanner.
func (p *Parser) Scan() (tok scanner.Token, pos scanner.Pos, lit string) { return p.s.Scan() }
// ScanIgnoreWhitespace scans the next non-whitespace and non-comment token.
func (p *Parser) ScanIgnoreWhitespace() (tok scanner.Token, pos scanner.Pos, lit string) {
for {
tok, pos, lit = p.Scan()
if tok == scanner.WS || tok == scanner.COMMENT {
continue
}
return
}
}
// Unscan pushes the previously read token back onto the buffer.
func (p *Parser) Unscan() {
p.s.Unscan()
}
// parseTokens parses all the given tokens one after the other.
// It returns an error if one of the token is missing.
func (p *Parser) parseTokens(tokens ...scanner.Token) error {
for _, t := range tokens {
if tok, pos, lit := p.ScanIgnoreWhitespace(); tok != t {
return newParseError(scanner.Tokstr(tok, lit), []string{t.String()}, pos)
}
}
return nil
}
// parseOptional parses a list of consecutive tokens. If the first token is not
// present, it unscans and return false. If the fist is present, all the others
// must be parsed otherwise an error is returned.
func (p *Parser) parseOptional(tokens ...scanner.Token) (bool, error) {
// Parse optional first token
if tok, _, _ := p.ScanIgnoreWhitespace(); tok != tokens[0] {
p.Unscan()
return false, nil
}
if len(tokens) == 1 {
return true, nil
}
err := p.parseTokens(tokens[1:]...)
return err == nil, err
}
// ParseError represents an error that occurred during parsing.
type ParseError struct {
Message string
Found string
Expected []string
Pos scanner.Pos
}
// newParseError returns a new instance of ParseError.
func newParseError(found string, expected []string, pos scanner.Pos) error {
return errors.Wrap(&ParseError{Found: found, Expected: expected, Pos: pos})
}
// Error returns the string representation of the error.
func (e *ParseError) Error() string {
if e.Message != "" {
return stringutil.Sprintf("%s at line %d, char %d", e.Message, e.Pos.Line+1, e.Pos.Char+1)
}
return stringutil.Sprintf("found %s, expected %s at line %d, char %d", e.Found, strings.Join(e.Expected, ", "), e.Pos.Line+1, e.Pos.Char+1)
}