Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support empty tags for all WHERE equality operations #6283

Merged
merged 3 commits into from
Apr 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
- [#6257](https://github.com/influxdata/influxdb/issues/6257): CreateShardGroup was incrementing meta data index even when it was idempotent.
- [#6223](https://github.com/influxdata/influxdb/issues/6223): Failure to start/run on Windows. Thanks @mvadu
- [#6229](https://github.com/influxdata/influxdb/issues/6229): Fixed aggregate queries with no GROUP BY to include the end time.
- [#6283](https://github.com/influxdata/influxdb/pull/6283): Fix GROUP BY tag to produce consistent results when a series has no tags.
- [#3773](https://github.com/influxdata/influxdb/issues/3773): Support empty tags for all WHERE equality operations.

## v0.12.0 [2016-04-05]
### Release Notes
Expand Down
91 changes: 91 additions & 0 deletions cmd/influxd/run/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4597,6 +4597,97 @@ func TestServer_Query_Where_With_Tags(t *testing.T) {
}
}

func TestServer_Query_With_EmptyTags(t *testing.T) {
t.Parallel()
s := OpenServer(NewConfig())
defer s.Close()

if err := s.CreateDatabaseAndRetentionPolicy("db0", newRetentionPolicyInfo("rp0", 1, 0)); err != nil {
t.Fatal(err)
}
if err := s.MetaClient.SetDefaultRetentionPolicy("db0", "rp0"); err != nil {
t.Fatal(err)
}

writes := []string{
fmt.Sprintf(`cpu value=1 %d`, mustParseTime(time.RFC3339Nano, "2009-11-10T23:00:02Z").UnixNano()),
fmt.Sprintf(`cpu,host=server01 value=2 %d`, mustParseTime(time.RFC3339Nano, "2009-11-10T23:00:03Z").UnixNano()),
}

test := NewTest("db0", "rp0")
test.writes = Writes{
&Write{data: strings.Join(writes, "\n")},
}

test.addQueries([]*Query{
&Query{
name: "where empty tag",
params: url.Values{"db": []string{"db0"}},
command: `select value from cpu where host = ''`,
exp: `{"results":[{"series":[{"name":"cpu","columns":["time","value"],"values":[["2009-11-10T23:00:02Z",1]]}]}]}`,
},
&Query{
name: "where not empty tag",
params: url.Values{"db": []string{"db0"}},
command: `select value from cpu where host != ''`,
exp: `{"results":[{"series":[{"name":"cpu","columns":["time","value"],"values":[["2009-11-10T23:00:03Z",2]]}]}]}`,
},
&Query{
name: "where regex all",
params: url.Values{"db": []string{"db0"}},
command: `select value from cpu where host =~ /.*/`,
exp: `{"results":[{"series":[{"name":"cpu","columns":["time","value"],"values":[["2009-11-10T23:00:02Z",1],["2009-11-10T23:00:03Z",2]]}]}]}`,
},
&Query{
name: "where regex none",
params: url.Values{"db": []string{"db0"}},
command: `select value from cpu where host !~ /.*/`,
exp: `{"results":[{}]}`,
},
&Query{
name: "where regex at least one char",
params: url.Values{"db": []string{"db0"}},
command: `select value from cpu where host =~ /.+/`,
exp: `{"results":[{"series":[{"name":"cpu","columns":["time","value"],"values":[["2009-11-10T23:00:03Z",2]]}]}]}`,
},
&Query{
name: "where regex not at least one char",
params: url.Values{"db": []string{"db0"}},
command: `select value from cpu where host !~ /.+/`,
exp: `{"results":[{"series":[{"name":"cpu","columns":["time","value"],"values":[["2009-11-10T23:00:02Z",1]]}]}]}`,
},
&Query{
name: "group by empty tag",
params: url.Values{"db": []string{"db0"}},
command: `select value from cpu group by host`,
exp: `{"results":[{"series":[{"name":"cpu","tags":{"host":""},"columns":["time","value"],"values":[["2009-11-10T23:00:02Z",1]]},{"name":"cpu","tags":{"host":"server01"},"columns":["time","value"],"values":[["2009-11-10T23:00:03Z",2]]}]}]}`,
},
&Query{
name: "group by missing tag",
params: url.Values{"db": []string{"db0"}},
command: `select value from cpu group by region`,
exp: `{"results":[{"series":[{"name":"cpu","tags":{"region":""},"columns":["time","value"],"values":[["2009-11-10T23:00:02Z",1],["2009-11-10T23:00:03Z",2]]}]}]}`,
},
}...)

for i, query := range test.queries {
if i == 0 {
if err := test.init(s); err != nil {
t.Fatalf("test init failed: %s", err)
}
}
if query.skip {
t.Logf("SKIP:: %s", query.name)
continue
}
if err := query.Execute(s); err != nil {
t.Error(query.Error(err))
} else if !query.success() {
t.Error(query.failureMessage())
}
}
}

func TestServer_Query_LimitAndOffset(t *testing.T) {
t.Parallel()
s := OpenServer(NewConfig())
Expand Down
2 changes: 1 addition & 1 deletion influxql/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (t *Tags) Value(k string) string {

// Subset returns a new tags object with a subset of the keys.
func (t *Tags) Subset(keys []string) Tags {
if t.m == nil || len(keys) == 0 {
if len(keys) == 0 {
return Tags{}
}

Expand Down
71 changes: 49 additions & 22 deletions tsdb/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,13 +763,8 @@ func (m *Measurement) idsForExpr(n *influxql.BinaryExpr) (SeriesIDs, influxql.Ex
return m.seriesIDs, n, nil
}

tagVals, ok := m.seriesByTagKeyValue[name.Val]
if name.Val != "_name" && !ok {
if n.Op == influxql.NEQ || n.Op == influxql.NEQREGEX {
return m.seriesIDs, &influxql.BooleanLiteral{Val: true}, nil
}
return nil, nil, nil
}
// Retrieve list of series with this tag key.
tagVals := m.seriesByTagKeyValue[name.Val]

// if we're looking for series with a specific tag value
if str, ok := value.(*influxql.StringLiteral); ok {
Expand All @@ -784,10 +779,23 @@ func (m *Measurement) idsForExpr(n *influxql.BinaryExpr) (SeriesIDs, influxql.Ex
}

if n.Op == influxql.EQ {
// return series that have a tag of specific value.
ids = tagVals[str.Val]
if str.Val != "" {
// return series that have a tag of specific value.
ids = tagVals[str.Val]
} else {
ids = m.seriesIDs
for k := range tagVals {
ids = ids.Reject(tagVals[k])
}
}
} else if n.Op == influxql.NEQ {
ids = m.seriesIDs.Reject(tagVals[str.Val])
if str.Val != "" {
ids = m.seriesIDs.Reject(tagVals[str.Val])
} else {
for k := range tagVals {
ids = ids.Union(tagVals[k])
}
}
}
return ids, &influxql.BooleanLiteral{Val: true}, nil
}
Expand All @@ -805,19 +813,38 @@ func (m *Measurement) idsForExpr(n *influxql.BinaryExpr) (SeriesIDs, influxql.Ex
return nil, &influxql.BooleanLiteral{Val: true}, nil
}

// The operation is a NEQREGEX, code must start by assuming all match, even
// series without any tags.
if n.Op == influxql.NEQREGEX {
ids = m.seriesIDs
}
// Check if we match the empty string to see if we should include series
// that are missing the tag.
empty := re.Val.MatchString("")

for k := range tagVals {
match := re.Val.MatchString(k)

if match && n.Op == influxql.EQREGEX {
ids = ids.Union(tagVals[k])
} else if match && n.Op == influxql.NEQREGEX {
ids = ids.Reject(tagVals[k])
// Gather the series that match the regex. If we should include the empty string,
// start with the list of all series and reject series that don't match our condition.
// If we should not include the empty string, include series that match our condition.
if empty && n.Op == influxql.EQREGEX {
ids = m.seriesIDs
for k := range tagVals {
if !re.Val.MatchString(k) {
ids = ids.Reject(tagVals[k])
}
}
} else if empty && n.Op == influxql.NEQREGEX {
for k := range tagVals {
if !re.Val.MatchString(k) {
ids = ids.Union(tagVals[k])
}
}
} else if !empty && n.Op == influxql.EQREGEX {
for k := range tagVals {
if re.Val.MatchString(k) {
ids = ids.Union(tagVals[k])
}
}
} else if !empty && n.Op == influxql.NEQREGEX {
ids = m.seriesIDs
for k := range tagVals {
if re.Val.MatchString(k) {
ids = ids.Reject(tagVals[k])
}
}
}
return ids, &influxql.BooleanLiteral{Val: true}, nil
Expand Down