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.
|
- [#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
|
- [#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.
|
- [#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
|
### Bugfixes
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ const (
|
||||||
|
|
||||||
// Parser represents an InfluxQL parser.
|
// Parser represents an InfluxQL parser.
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
s *bufScanner
|
s *bufScanner
|
||||||
|
params map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParser returns a new instance of Parser.
|
// NewParser returns a new instance of Parser.
|
||||||
|
@ -31,6 +32,10 @@ func NewParser(r io.Reader) *Parser {
|
||||||
return &Parser{s: newBufScanner(r)}
|
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.
|
// ParseQuery parses a query string and returns its AST representation.
|
||||||
func ParseQuery(s string) (*Query, error) { return NewParser(strings.NewReader(s)).ParseQuery() }
|
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 nil, &ParseError{Message: err.Error(), Pos: pos}
|
||||||
}
|
}
|
||||||
return &RegexLiteral{Val: re}, nil
|
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:
|
default:
|
||||||
return nil, newParseError(tokstr(tok, lit), []string{"identifier", "string", "number", "bool"}, pos)
|
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()
|
now := time.Now()
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
skip bool
|
skip bool
|
||||||
s string
|
s string
|
||||||
stmt influxql.Statement
|
params map[string]interface{}
|
||||||
err string
|
stmt influxql.Statement
|
||||||
|
err string
|
||||||
}{
|
}{
|
||||||
// SELECT * statement
|
// 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
|
// See issues https://github.com/influxdata/influxdb/issues/1647
|
||||||
// and https://github.com/influxdata/influxdb/issues/4404
|
// and https://github.com/influxdata/influxdb/issues/4404
|
||||||
// DELETE statement
|
// DELETE statement
|
||||||
|
@ -2199,7 +2219,11 @@ func TestParser_ParseStatement(t *testing.T) {
|
||||||
if tt.skip {
|
if tt.skip {
|
||||||
continue
|
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...
|
// We are memoizing a field so for testing we need to...
|
||||||
if s, ok := tt.stmt.(*influxql.SelectStatement); ok {
|
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 s.scanNumber()
|
||||||
}
|
}
|
||||||
return DOT, pos, ""
|
return DOT, pos, ""
|
||||||
|
case '$':
|
||||||
|
tok, _, lit := s.scanIdent()
|
||||||
|
if tok == IDENT {
|
||||||
|
tok = BOUNDPARAM
|
||||||
|
}
|
||||||
|
return tok, pos, lit
|
||||||
case '+', '-':
|
case '+', '-':
|
||||||
return s.scanNumber()
|
return s.scanNumber()
|
||||||
case '*':
|
case '*':
|
||||||
|
|
|
@ -70,6 +70,8 @@ func TestScanner_Scan(t *testing.T) {
|
||||||
{s: `"foo\"bar\""`, tok: influxql.IDENT, lit: `foo"bar"`},
|
{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: "", pos: influxql.Pos{Line: 0, Char: 3}},
|
||||||
{s: `"test`, tok: influxql.BADSTRING, lit: `test`},
|
{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: `true`, tok: influxql.TRUE},
|
||||||
{s: `false`, tok: influxql.FALSE},
|
{s: `false`, tok: influxql.FALSE},
|
||||||
|
|
|
@ -17,6 +17,7 @@ const (
|
||||||
literalBeg
|
literalBeg
|
||||||
// IDENT and the following are InfluxQL literal tokens.
|
// IDENT and the following are InfluxQL literal tokens.
|
||||||
IDENT // main
|
IDENT // main
|
||||||
|
BOUNDPARAM // $param
|
||||||
NUMBER // 12345.67
|
NUMBER // 12345.67
|
||||||
INTEGER // 12345
|
INTEGER // 12345
|
||||||
DURATIONVAL // 13h
|
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.
|
// Do this before anything else so a parsing error doesn't leak passwords.
|
||||||
sanitize(r)
|
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.
|
// Parse query from query string.
|
||||||
query, err := p.ParseQuery()
|
query, err := p.ParseQuery()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue