Merge pull request #6634 from influxdata/js-2926-support-bound-parameters
Support bound parameters in the parserpull/6666/head
commit
c89005f046
|
@ -13,6 +13,7 @@
|
|||
- [#6519](https://github.com/influxdata/influxdb/issues/6519): Support cast syntax for selecting a specific type.
|
||||
- [#6654](https://github.com/influxdata/influxdb/pull/6654): Add new HTTP statistics to monitoring
|
||||
- [#6664](https://github.com/influxdata/influxdb/pull/6664): Adds monitoring statistic for on-disk shard size.
|
||||
- [#2926](https://github.com/influxdata/influxdb/issues/2926): Support bound parameters in the parser.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ const (
|
|||
|
||||
// Parser represents an InfluxQL parser.
|
||||
type Parser struct {
|
||||
s *bufScanner
|
||||
s *bufScanner
|
||||
params map[string]interface{}
|
||||
}
|
||||
|
||||
// NewParser returns a new instance of Parser.
|
||||
|
@ -31,6 +32,10 @@ func NewParser(r io.Reader) *Parser {
|
|||
return &Parser{s: newBufScanner(r)}
|
||||
}
|
||||
|
||||
func (p *Parser) SetParams(params map[string]interface{}) {
|
||||
p.params = params
|
||||
}
|
||||
|
||||
// ParseQuery parses a query string and returns its AST representation.
|
||||
func ParseQuery(s string) (*Query, error) { return NewParser(strings.NewReader(s)).ParseQuery() }
|
||||
|
||||
|
@ -2430,6 +2435,24 @@ func (p *Parser) parseUnaryExpr() (Expr, error) {
|
|||
return nil, &ParseError{Message: err.Error(), Pos: pos}
|
||||
}
|
||||
return &RegexLiteral{Val: re}, nil
|
||||
case BOUNDPARAM:
|
||||
v, ok := p.params[lit]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing parameter: %s", lit)
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
return &NumberLiteral{Val: v}, nil
|
||||
case int64:
|
||||
return &IntegerLiteral{Val: v}, nil
|
||||
case string:
|
||||
return &StringLiteral{Val: v}, nil
|
||||
case bool:
|
||||
return &BooleanLiteral{Val: v}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to bind parameter with type %T", v)
|
||||
}
|
||||
default:
|
||||
return nil, newParseError(tokstr(tok, lit), []string{"identifier", "string", "number", "bool"}, pos)
|
||||
}
|
||||
|
|
|
@ -64,10 +64,11 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
now := time.Now()
|
||||
|
||||
var tests = []struct {
|
||||
skip bool
|
||||
s string
|
||||
stmt influxql.Statement
|
||||
err string
|
||||
skip bool
|
||||
s string
|
||||
params map[string]interface{}
|
||||
stmt influxql.Statement
|
||||
err string
|
||||
}{
|
||||
// SELECT * statement
|
||||
{
|
||||
|
@ -820,6 +821,25 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
// SELECT statement with a bound parameter
|
||||
{
|
||||
s: `SELECT value FROM cpu WHERE value > $value`,
|
||||
params: map[string]interface{}{
|
||||
"value": int64(2),
|
||||
},
|
||||
stmt: &influxql.SelectStatement{
|
||||
IsRawQuery: true,
|
||||
Fields: []*influxql.Field{{
|
||||
Expr: &influxql.VarRef{Val: "value"}}},
|
||||
Sources: []influxql.Source{&influxql.Measurement{Name: "cpu"}},
|
||||
Condition: &influxql.BinaryExpr{
|
||||
Op: influxql.GT,
|
||||
LHS: &influxql.VarRef{Val: "value"},
|
||||
RHS: &influxql.IntegerLiteral{Val: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// See issues https://github.com/influxdata/influxdb/issues/1647
|
||||
// and https://github.com/influxdata/influxdb/issues/4404
|
||||
// DELETE statement
|
||||
|
@ -2199,7 +2219,11 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
if tt.skip {
|
||||
continue
|
||||
}
|
||||
stmt, err := influxql.NewParser(strings.NewReader(tt.s)).ParseStatement()
|
||||
p := influxql.NewParser(strings.NewReader(tt.s))
|
||||
if tt.params != nil {
|
||||
p.SetParams(tt.params)
|
||||
}
|
||||
stmt, err := p.ParseStatement()
|
||||
|
||||
// We are memoizing a field so for testing we need to...
|
||||
if s, ok := tt.stmt.(*influxql.SelectStatement); ok {
|
||||
|
|
|
@ -53,6 +53,12 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) {
|
|||
return s.scanNumber()
|
||||
}
|
||||
return DOT, pos, ""
|
||||
case '$':
|
||||
tok, _, lit := s.scanIdent()
|
||||
if tok == IDENT {
|
||||
tok = BOUNDPARAM
|
||||
}
|
||||
return tok, pos, lit
|
||||
case '+', '-':
|
||||
return s.scanNumber()
|
||||
case '*':
|
||||
|
|
|
@ -70,6 +70,8 @@ func TestScanner_Scan(t *testing.T) {
|
|||
{s: `"foo\"bar\""`, tok: influxql.IDENT, lit: `foo"bar"`},
|
||||
{s: `test"`, tok: influxql.BADSTRING, lit: "", pos: influxql.Pos{Line: 0, Char: 3}},
|
||||
{s: `"test`, tok: influxql.BADSTRING, lit: `test`},
|
||||
{s: `$host`, tok: influxql.BOUNDPARAM, lit: `host`},
|
||||
{s: `$"host param"`, tok: influxql.BOUNDPARAM, lit: `host param`},
|
||||
|
||||
{s: `true`, tok: influxql.TRUE},
|
||||
{s: `false`, tok: influxql.FALSE},
|
||||
|
|
|
@ -17,6 +17,7 @@ const (
|
|||
literalBeg
|
||||
// IDENT and the following are InfluxQL literal tokens.
|
||||
IDENT // main
|
||||
BOUNDPARAM // $param
|
||||
NUMBER // 12345.67
|
||||
INTEGER // 12345
|
||||
DURATIONVAL // 13h
|
||||
|
|
|
@ -280,6 +280,36 @@ func (h *Handler) serveQuery(w http.ResponseWriter, r *http.Request, user *meta.
|
|||
// Do this before anything else so a parsing error doesn't leak passwords.
|
||||
sanitize(r)
|
||||
|
||||
// Parse the parameters
|
||||
rawParams := r.FormValue("params")
|
||||
if rawParams != "" {
|
||||
var params map[string]interface{}
|
||||
decoder := json.NewDecoder(strings.NewReader(rawParams))
|
||||
decoder.UseNumber()
|
||||
if err := decoder.Decode(¶ms); err != nil {
|
||||
h.httpError(w, "error parsing query parameters: "+err.Error(), pretty, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert json.Number into int64 and float64 values
|
||||
for k, v := range params {
|
||||
if v, ok := v.(json.Number); ok {
|
||||
var err error
|
||||
if strings.Contains(string(v), ".") {
|
||||
params[k], err = v.Float64()
|
||||
} else {
|
||||
params[k], err = v.Int64()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
h.httpError(w, "error parsing json value: "+err.Error(), pretty, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
p.SetParams(params)
|
||||
}
|
||||
|
||||
// Parse query from query string.
|
||||
query, err := p.ParseQuery()
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue