Ejemplo n.º 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
	}
}
Ejemplo n.º 2
0
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
}
Ejemplo n.º 3
0
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)
	}
}