func (p *Parser) parseExprWithOperators(precedence ast.OpPrecedence) ast.Expr { lhs := p.parseBaseExpression() if p.tok == token.LEFT_PAREN { // TODO: functions like MAX(), MIN(), AVERAGE() p.eatUnimplemented("expression") } else if !p.tok.IsOperator() { return lhs } op, exists := p.rules.Operators.Lookup(p.tok.String(), ast.Infix) if !exists { msg := `statement includes '` + p.tok.String() + `', but it is not defined as an operator` p.error(p.scanner.Pos(), msg) } consumable := ast.MaxPrecedence for (op.Kind == ast.Infix) && (precedence <= op.Precedence && op.Precedence <= consumable) { p.next() // eat operator rhs := p.parseExprWithOperators(rightPrec(op)) lhs = ast.Binary(lhs, op.Type, rhs) if p.tok.IsOperator() { var exists bool op, exists = p.rules.Operators.Lookup(p.tok.String(), ast.Infix) if !exists { msg := `statement includes '` + p.tok.String() + `', but it is not defined as an operator` p.error(p.scanner.Pos(), msg) } consumable = nextPrec(op) } else { break } } return lhs }
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) } }