Support regex and other operations for selecting the key in SHOW TAG VALUES
This adds support for using regex expressions in SHOW TAG VALUES when selecting the key. Also supporting the `!=` operation for the comparison. Now you can do any of the following: SHOW TAG VALUES WITH KEY != "region" SHOW TAG VALUES WITH KEY =~ /region/ SHOW TAG VALUES WITH KEY !~ /region/ It also adds a new SetLiteral AST node that will potentially be used in the future to allow set operations for other comparisons in the future. Fixes #4532.pull/6564/head
parent
48da935314
commit
9837de793c
|
@ -24,6 +24,7 @@
|
|||
- [#3733](https://github.com/influxdata/influxdb/issues/3733): Modify the default retention policy name and make it configurable.
|
||||
- [#5655](https://github.com/influxdata/influxdb/issues/5655): Support specifying a retention policy for the graphite service.
|
||||
- [#6820](https://github.com/influxdata/influxdb/issues/6820): Add NodeID to execution options
|
||||
- [#4532](https://github.com/influxdata/influxdb/issues/4532): Support regex selection in SHOW TAG VALUES for the key.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
|
|
|
@ -5553,12 +5553,24 @@ func TestServer_Query_ShowTagKeys(t *testing.T) {
|
|||
exp: `{"results":[{"series":[{"name":"cpu","columns":["key","value"],"values":[["host","server01"],["host","server02"]]},{"name":"disk","columns":["key","value"],"values":[["host","server03"]]},{"name":"gpu","columns":["key","value"],"values":[["host","server02"],["host","server03"]]}]}]}`,
|
||||
params: url.Values{"db": []string{"db0"}},
|
||||
},
|
||||
&Query{
|
||||
name: "show tag values with key regex",
|
||||
command: "SHOW TAG VALUES WITH KEY =~ /ho/",
|
||||
exp: `{"results":[{"series":[{"name":"cpu","columns":["key","value"],"values":[["host","server01"],["host","server02"]]},{"name":"disk","columns":["key","value"],"values":[["host","server03"]]},{"name":"gpu","columns":["key","value"],"values":[["host","server02"],["host","server03"]]}]}]}`,
|
||||
params: url.Values{"db": []string{"db0"}},
|
||||
},
|
||||
&Query{
|
||||
name: `show tag values with key and where`,
|
||||
command: `SHOW TAG VALUES FROM cpu WITH KEY = host WHERE region = 'uswest'`,
|
||||
exp: `{"results":[{"series":[{"name":"cpu","columns":["key","value"],"values":[["host","server01"]]}]}]}`,
|
||||
params: url.Values{"db": []string{"db0"}},
|
||||
},
|
||||
&Query{
|
||||
name: `show tag values with key regex and where`,
|
||||
command: `SHOW TAG VALUES FROM cpu WITH KEY =~ /ho/ WHERE region = 'uswest'`,
|
||||
exp: `{"results":[{"series":[{"name":"cpu","columns":["key","value"],"values":[["host","server01"]]}]}]}`,
|
||||
params: url.Values{"db": []string{"db0"}},
|
||||
},
|
||||
&Query{
|
||||
name: `show tag values with key and where matches the regular expression`,
|
||||
command: `SHOW TAG VALUES WITH KEY = host WHERE region =~ /ca.*/`,
|
||||
|
@ -5589,6 +5601,12 @@ func TestServer_Query_ShowTagKeys(t *testing.T) {
|
|||
exp: `{"results":[{"series":[{"name":"cpu","columns":["key","value"],"values":[["host","server01"],["region","uswest"]]}]}]}`,
|
||||
params: url.Values{"db": []string{"db0"}},
|
||||
},
|
||||
&Query{
|
||||
name: `show tag values with key regex and where does not match the regular expression`,
|
||||
command: `SHOW TAG VALUES FROM cpu WITH KEY =~ /(host|region)/ WHERE region = 'uswest'`,
|
||||
exp: `{"results":[{"series":[{"name":"cpu","columns":["key","value"],"values":[["host","server01"],["region","uswest"]]}]}]}`,
|
||||
params: url.Values{"db": []string{"db0"}},
|
||||
},
|
||||
&Query{
|
||||
name: `show tag values with key and measurement matches regular expression`,
|
||||
command: `SHOW TAG VALUES FROM /[cg]pu/ WITH KEY = host`,
|
||||
|
|
|
@ -158,6 +158,7 @@ func (*nilLiteral) node() {}
|
|||
func (*NumberLiteral) node() {}
|
||||
func (*ParenExpr) node() {}
|
||||
func (*RegexLiteral) node() {}
|
||||
func (*ListLiteral) node() {}
|
||||
func (*SortField) node() {}
|
||||
func (SortFields) node() {}
|
||||
func (Sources) node() {}
|
||||
|
@ -273,6 +274,7 @@ func (*nilLiteral) expr() {}
|
|||
func (*NumberLiteral) expr() {}
|
||||
func (*ParenExpr) expr() {}
|
||||
func (*RegexLiteral) expr() {}
|
||||
func (*ListLiteral) expr() {}
|
||||
func (*StringLiteral) expr() {}
|
||||
func (*TimeLiteral) expr() {}
|
||||
func (*VarRef) expr() {}
|
||||
|
@ -290,6 +292,7 @@ func (*IntegerLiteral) literal() {}
|
|||
func (*nilLiteral) literal() {}
|
||||
func (*NumberLiteral) literal() {}
|
||||
func (*RegexLiteral) literal() {}
|
||||
func (*ListLiteral) literal() {}
|
||||
func (*StringLiteral) literal() {}
|
||||
func (*TimeLiteral) literal() {}
|
||||
|
||||
|
@ -2769,8 +2772,11 @@ type ShowTagValuesStatement struct {
|
|||
// Data source that fields are extracted from.
|
||||
Sources Sources
|
||||
|
||||
// Tag key(s) to pull values from.
|
||||
TagKeys []string
|
||||
// Operation to use when selecting tag key(s).
|
||||
Op Token
|
||||
|
||||
// Literal to compare the tag key(s) with.
|
||||
TagKeyExpr Literal
|
||||
|
||||
// An expression evaluated on data point.
|
||||
Condition Expr
|
||||
|
@ -2795,14 +2801,10 @@ func (s *ShowTagValuesStatement) String() string {
|
|||
_, _ = buf.WriteString(" FROM ")
|
||||
_, _ = buf.WriteString(s.Sources.String())
|
||||
}
|
||||
_, _ = buf.WriteString(" WITH KEY IN (")
|
||||
for idx, tagKey := range s.TagKeys {
|
||||
if idx != 0 {
|
||||
_, _ = buf.WriteString(", ")
|
||||
}
|
||||
_, _ = buf.WriteString(QuoteIdent(tagKey))
|
||||
}
|
||||
_, _ = buf.WriteString(")")
|
||||
_, _ = buf.WriteString(" WITH KEY ")
|
||||
_, _ = buf.WriteString(s.Op.String())
|
||||
_, _ = buf.WriteString(" ")
|
||||
_, _ = buf.WriteString(s.TagKeyExpr.String())
|
||||
if s.Condition != nil {
|
||||
_, _ = buf.WriteString(" WHERE ")
|
||||
_, _ = buf.WriteString(s.Condition.String())
|
||||
|
@ -3247,6 +3249,25 @@ func isFalseLiteral(expr Expr) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// ListLiteral represents a list of strings literal.
|
||||
type ListLiteral struct {
|
||||
Vals []string
|
||||
}
|
||||
|
||||
// String returns a string representation of the literal.
|
||||
func (s *ListLiteral) String() string {
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.WriteString("(")
|
||||
for idx, tagKey := range s.Vals {
|
||||
if idx != 0 {
|
||||
_, _ = buf.WriteString(", ")
|
||||
}
|
||||
_, _ = buf.WriteString(QuoteIdent(tagKey))
|
||||
}
|
||||
_, _ = buf.WriteString(")")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// StringLiteral represents a string literal.
|
||||
type StringLiteral struct {
|
||||
Val string
|
||||
|
|
|
@ -1218,7 +1218,7 @@ func (p *Parser) parseShowTagValuesStatement() (*ShowTagValuesStatement, error)
|
|||
}
|
||||
|
||||
// Parse required WITH KEY.
|
||||
if stmt.TagKeys, err = p.parseTagKeys(); err != nil {
|
||||
if stmt.Op, stmt.TagKeyExpr, err = p.parseTagKeyExpr(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -1246,44 +1246,52 @@ func (p *Parser) parseShowTagValuesStatement() (*ShowTagValuesStatement, error)
|
|||
}
|
||||
|
||||
// parseTagKeys parses a string and returns a list of tag keys.
|
||||
func (p *Parser) parseTagKeys() ([]string, error) {
|
||||
func (p *Parser) parseTagKeyExpr() (Token, Literal, error) {
|
||||
var err error
|
||||
|
||||
// Parse required WITH KEY tokens.
|
||||
if err := p.parseTokens([]Token{WITH, KEY}); err != nil {
|
||||
return nil, err
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
var tagKeys []string
|
||||
|
||||
// Parse required IN or EQ token.
|
||||
// Parse required IN, EQ, or EQREGEX token.
|
||||
if tok, pos, lit := p.scanIgnoreWhitespace(); tok == IN {
|
||||
// Parse required ( token.
|
||||
if tok, pos, lit = p.scanIgnoreWhitespace(); tok != LPAREN {
|
||||
return nil, newParseError(tokstr(tok, lit), []string{"("}, pos)
|
||||
return 0, nil, newParseError(tokstr(tok, lit), []string{"("}, pos)
|
||||
}
|
||||
|
||||
// Parse tag key list.
|
||||
var tagKeys []string
|
||||
if tagKeys, err = p.parseIdentList(); err != nil {
|
||||
return nil, err
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// Parse required ) token.
|
||||
if tok, pos, lit = p.scanIgnoreWhitespace(); tok != RPAREN {
|
||||
return nil, newParseError(tokstr(tok, lit), []string{")"}, pos)
|
||||
return 0, nil, newParseError(tokstr(tok, lit), []string{")"}, pos)
|
||||
}
|
||||
} else if tok == EQ {
|
||||
return IN, &ListLiteral{Vals: tagKeys}, nil
|
||||
} else if tok == EQ || tok == NEQ {
|
||||
// Parse required tag key.
|
||||
ident, err := p.parseIdent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return 0, nil, err
|
||||
}
|
||||
tagKeys = append(tagKeys, ident)
|
||||
return tok, &StringLiteral{Val: ident}, nil
|
||||
} else if tok == EQREGEX || tok == NEQREGEX {
|
||||
re, err := p.parseRegex()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
} else if re == nil {
|
||||
// parseRegex can return an empty type, but we need it to be present
|
||||
tok, pos, lit := p.scanIgnoreWhitespace()
|
||||
return 0, nil, newParseError(tokstr(tok, lit), []string{"regex"}, pos)
|
||||
}
|
||||
return tok, re, nil
|
||||
} else {
|
||||
return nil, newParseError(tokstr(tok, lit), []string{"IN", "="}, pos)
|
||||
return 0, nil, newParseError(tokstr(tok, lit), []string{"IN", "=", "=~"}, pos)
|
||||
}
|
||||
|
||||
return tagKeys, nil
|
||||
}
|
||||
|
||||
// parseShowUsersStatement parses a string and returns a ShowUsersStatement.
|
||||
|
|
|
@ -1259,8 +1259,9 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
skip: true,
|
||||
s: `SHOW TAG VALUES FROM src WITH KEY = region WHERE region = 'uswest' ORDER BY ASC, field1, field2 DESC LIMIT 10`,
|
||||
stmt: &influxql.ShowTagValuesStatement{
|
||||
Sources: []influxql.Source{&influxql.Measurement{Name: "src"}},
|
||||
TagKeys: []string{"region"},
|
||||
Sources: []influxql.Source{&influxql.Measurement{Name: "src"}},
|
||||
Op: influxql.EQ,
|
||||
TagKeyExpr: &influxql.StringLiteral{Val: "region"},
|
||||
Condition: &influxql.BinaryExpr{
|
||||
Op: influxql.EQ,
|
||||
LHS: &influxql.VarRef{Val: "region"},
|
||||
|
@ -1279,8 +1280,9 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
{
|
||||
s: `SHOW TAG VALUES FROM cpu WITH KEY IN (region, host) WHERE region = 'uswest'`,
|
||||
stmt: &influxql.ShowTagValuesStatement{
|
||||
Sources: []influxql.Source{&influxql.Measurement{Name: "cpu"}},
|
||||
TagKeys: []string{"region", "host"},
|
||||
Sources: []influxql.Source{&influxql.Measurement{Name: "cpu"}},
|
||||
Op: influxql.IN,
|
||||
TagKeyExpr: &influxql.ListLiteral{Vals: []string{"region", "host"}},
|
||||
Condition: &influxql.BinaryExpr{
|
||||
Op: influxql.EQ,
|
||||
LHS: &influxql.VarRef{Val: "region"},
|
||||
|
@ -1293,8 +1295,9 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
{
|
||||
s: `SHOW TAG VALUES FROM cpu WITH KEY IN (region,service,host)WHERE region = 'uswest'`,
|
||||
stmt: &influxql.ShowTagValuesStatement{
|
||||
Sources: []influxql.Source{&influxql.Measurement{Name: "cpu"}},
|
||||
TagKeys: []string{"region", "service", "host"},
|
||||
Sources: []influxql.Source{&influxql.Measurement{Name: "cpu"}},
|
||||
Op: influxql.IN,
|
||||
TagKeyExpr: &influxql.ListLiteral{Vals: []string{"region", "service", "host"}},
|
||||
Condition: &influxql.BinaryExpr{
|
||||
Op: influxql.EQ,
|
||||
LHS: &influxql.VarRef{Val: "region"},
|
||||
|
@ -1307,7 +1310,8 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
{
|
||||
s: `SHOW TAG VALUES WITH KEY = host WHERE region = 'uswest'`,
|
||||
stmt: &influxql.ShowTagValuesStatement{
|
||||
TagKeys: []string{"host"},
|
||||
Op: influxql.EQ,
|
||||
TagKeyExpr: &influxql.StringLiteral{Val: "host"},
|
||||
Condition: &influxql.BinaryExpr{
|
||||
Op: influxql.EQ,
|
||||
LHS: &influxql.VarRef{Val: "region"},
|
||||
|
@ -1325,7 +1329,8 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
Regex: &influxql.RegexLiteral{Val: regexp.MustCompile(`[cg]pu`)},
|
||||
},
|
||||
},
|
||||
TagKeys: []string{"host"},
|
||||
Op: influxql.EQ,
|
||||
TagKeyExpr: &influxql.StringLiteral{Val: "host"},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -1333,7 +1338,8 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
{
|
||||
s: `SHOW TAG VALUES WITH KEY = "host" WHERE region = 'uswest'`,
|
||||
stmt: &influxql.ShowTagValuesStatement{
|
||||
TagKeys: []string{`host`},
|
||||
Op: influxql.EQ,
|
||||
TagKeyExpr: &influxql.StringLiteral{Val: `host`},
|
||||
Condition: &influxql.BinaryExpr{
|
||||
Op: influxql.EQ,
|
||||
LHS: &influxql.VarRef{Val: "region"},
|
||||
|
@ -1342,6 +1348,15 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
// SHOW TAG VALUES WITH KEY =~ /<regex>/
|
||||
{
|
||||
s: `SHOW TAG VALUES WITH KEY =~ /(host|region)/`,
|
||||
stmt: &influxql.ShowTagValuesStatement{
|
||||
Op: influxql.EQREGEX,
|
||||
TagKeyExpr: &influxql.RegexLiteral{Val: regexp.MustCompile(`(host|region)`)},
|
||||
},
|
||||
},
|
||||
|
||||
// SHOW USERS
|
||||
{
|
||||
s: `SHOW USERS`,
|
||||
|
|
|
@ -90,9 +90,9 @@ func rewriteShowTagValuesStatement(stmt *ShowTagValuesStatement) (Statement, err
|
|||
}
|
||||
|
||||
condition := stmt.Condition
|
||||
if len(stmt.TagKeys) > 0 {
|
||||
var expr Expr
|
||||
for _, tagKey := range stmt.TagKeys {
|
||||
var expr Expr
|
||||
if list, ok := stmt.TagKeyExpr.(*ListLiteral); ok {
|
||||
for _, tagKey := range list.Vals {
|
||||
tagExpr := &BinaryExpr{
|
||||
Op: EQ,
|
||||
LHS: &VarRef{Val: "_tagKey"},
|
||||
|
@ -109,16 +109,22 @@ func rewriteShowTagValuesStatement(stmt *ShowTagValuesStatement) (Statement, err
|
|||
expr = tagExpr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
expr = &BinaryExpr{
|
||||
Op: stmt.Op,
|
||||
LHS: &VarRef{Val: "_tagKey"},
|
||||
RHS: stmt.TagKeyExpr,
|
||||
}
|
||||
}
|
||||
|
||||
// Set condition or "AND" together.
|
||||
if condition == nil {
|
||||
condition = expr
|
||||
} else {
|
||||
condition = &BinaryExpr{
|
||||
Op: AND,
|
||||
LHS: &ParenExpr{Expr: condition},
|
||||
RHS: &ParenExpr{Expr: expr},
|
||||
}
|
||||
// Set condition or "AND" together.
|
||||
if condition == nil {
|
||||
condition = expr
|
||||
} else {
|
||||
condition = &BinaryExpr{
|
||||
Op: AND,
|
||||
LHS: &ParenExpr{Expr: condition},
|
||||
RHS: &ParenExpr{Expr: expr},
|
||||
}
|
||||
}
|
||||
condition = rewriteSourcesCondition(stmt.Sources, condition)
|
||||
|
|
|
@ -111,6 +111,14 @@ func TestRewriteStatement(t *testing.T) {
|
|||
stmt: `SHOW TAG VALUES FROM mydb.myrp1.cpu WITH KEY IN (region, host)`,
|
||||
s: `SELECT _tagKey AS "key", value FROM mydb.myrp1._tags WHERE (_name = 'cpu') AND (_tagKey = 'region' OR _tagKey = 'host')`,
|
||||
},
|
||||
{
|
||||
stmt: `SHOW TAG VALUES FROM cpu WITH KEY =~ /(region|host)/`,
|
||||
s: `SELECT _tagKey AS "key", value FROM _tags WHERE (_name = 'cpu') AND (_tagKey =~ /(region|host)/)`,
|
||||
},
|
||||
{
|
||||
stmt: `SHOW TAG VALUES FROM mydb.myrp1.cpu WITH KEY =~ /(region|host)/`,
|
||||
s: `SELECT _tagKey AS "key", value FROM mydb.myrp1._tags WHERE (_name = 'cpu') AND (_tagKey =~ /(region|host)/)`,
|
||||
},
|
||||
{
|
||||
stmt: `SELECT value FROM cpu`,
|
||||
s: `SELECT value FROM cpu`,
|
||||
|
|
Loading…
Reference in New Issue