func (p *Parser) parseBaseExpression() ast.Expr { /* handle prefix operator */ if p.tok.IsOperator() { op, exists := p.rules.Operators.Lookup(p.tok.String(), ast.Prefix) if !exists { msg := `statement includes '` + p.tok.String() + `', but it is not defined as a unary operator` p.error(p.scanner.Pos(), msg) } p.next() // eat operator expr := p.parseExprWithOperators(op.Precedence) return ast.Unary(op.Type, expr) } switch p.tok { case token.IDENT: ident := ast.Name(p.lit) p.next() return ident case token.QUOTED_IDENT: ident := ast.Quoted(p.lit) p.next() return ident case token.STRING, token.NUMBER: lit := ast.Lit(p.lit) p.next() return lit default: p.eatUnimplemented("expression") return nil } }
func (p *Parser) parseSelect() *ast.SelectStmt { p.expect(token.SELECT) stmt := &ast.SelectStmt{} stmt.Type = ast.SELECT_ALL switch p.tok { case token.ALL: p.next() case token.DISTINCT: stmt.Type = ast.DISTINCT p.next() case token.DISTINCTROW: if p.rules.CanSelectDistinctRow { stmt.Type = ast.DISTINCT_ROW p.next() } else { msg := `statement includes SELECT "DISTINCTROW", but CanSelectDistinctRow is false` p.error(p.scanner.Pos(), msg) } } if p.tok == token.ASTERISK { stmt.Star = true p.next() } else { stmt.Select = []ast.Expr{p.parseExpression()} for p.tok == token.COMMA { p.next() // eat comma stmt.Select = append(stmt.Select, p.parseExpression()) } } // NOTE: The FROM clause is sometimes optional, but since this would be an // error in most common uses cases, the default will be that it is required // even for dialects where it is technically optional. if p.rules.CanSelectWithoutFrom && p.tok == token.EOS { return stmt } p.expect(token.FROM) switch p.tok { case token.IDENT: stmt.From = ast.Name(p.lit) p.next() case token.QUOTED_IDENT: stmt.From = ast.Quoted(p.lit) p.next() default: p.expected("a table name") } if p.tok == token.WHERE { p.next() // eat WHERE stmt.Where = p.parseExpression() } // if p.tok == token.GROUP { // panic("TODO: parse GROUP BY") // } // // if p.tok == token.HAVING { // panic("TODO: parse HAVING") // } // // if p.tok == token.ORDER { // panic("TODO: parse ORDER") // } // // if p.tok == token.LIMIT { // panic("TODO: parse LIMIT") // } p.eatUnimplemented("clause") return stmt }
func TestParseSelect(t *testing.T) { examples := []struct { Input string Rules Ruleset Result ast.Stmt Trace bool // for debugging }{ {Input: `SELECT * FROM mytable`, // basics Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, From: ast.Name("mytable"), Star: true, }}, {Input: `SELECT * FROM mytable;`, // with semicolon Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, From: ast.Name("mytable"), Star: true, }}, {Input: `SELECT * FROM "mytable"`, // doublequotes (ansi) Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, From: ast.Quoted("mytable"), Star: true, }}, {Input: "SELECT * FROM `mytable`", // backticks (mysql) Rules: MysqlRuleset, Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, From: ast.Quoted("mytable"), Star: true, }}, {Input: `SELECT foo, bar FROM mytable`, // with columns Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, From: ast.Name("mytable"), Select: []ast.Expr{ast.Name("foo"), ast.Name("bar")}, }}, {Input: `SELECT "foo", "bar" FROM mytable`, // with quoted columns Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, From: ast.Name("mytable"), Select: []ast.Expr{ast.Quoted("foo"), ast.Quoted("bar")}, }}, {Input: `SELECT ALL * FROM mytable`, // ALL Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, From: ast.Name("mytable"), Star: true, }}, {Input: `SELECT DISTINCT * FROM mytable`, // DISTINCT Result: &ast.SelectStmt{ Type: ast.DISTINCT, From: ast.Name("mytable"), Star: true, }}, {Input: `SELECT DISTINCTROW * FROM mytable`, // DISTINCT ROW (mysql) Rules: MysqlRuleset, Result: &ast.SelectStmt{ Type: ast.DISTINCT_ROW, From: ast.Name("mytable"), Star: true, }}, {Input: `SELECT * FROM mytable WHERE id = 3`, // simple WHERE clause Rules: AnsiRuleset, Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, Star: true, From: ast.Name("mytable"), Where: ast.Binary(ast.Name("id"), ast.EQUAL, ast.Lit("3")), }}, // a WHERE clause with nested expressions (binary and unary) {Input: `SELECT * FROM mytable WHERE kind != "muppet" AND 5 < -size`, Rules: MysqlRuleset, Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, Star: true, From: ast.Name("mytable"), Where: ast.Binary( ast.Binary(ast.Name("kind"), ast.NOT_EQUAL, ast.Lit(`"muppet"`)), ast.AND, ast.Binary(ast.Lit("5"), ast.LESS, ast.Unary(ast.NEGATE, ast.Name("size"))), ), }}, // allow table-less select if someone says its ok {Input: `SELECT *`, // TODO: eventually I'd like this to be `SELECT 1+1;` Rules: Ruleset{CanSelectWithoutFrom: true}, Result: &ast.SelectStmt{Type: ast.SELECT_ALL, Star: true}}, // allow unimplmented clauses if someone says its ok {Input: `SELECT * FROM mytable PROCEDURE compute(foo)`, Rules: Ruleset{AllowNotImplemented: true}, Result: &ast.SelectStmt{ Type: ast.SELECT_ALL, From: ast.Name("mytable"), Star: true, }}, } for _, example := range examples { parser := New([]byte(example.Input), example.Rules) if example.Trace { parser.Trace = os.Stdout } stmt, err := parser.ParseStatement() expect.Nil(t, err, "Error for `"+example.Input+"`") expect.Equal(t, stmt, example.Result, example.Input) } }