Exemple #1
0
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 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)
	}
}