diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f64e6a5c..077f857a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - [#2596](https://github.com/influxdb/influxdb/pull/2596): RC30: `panic: runtime error: index out of range` when insert data points. - [#2592](https://github.com/influxdb/influxdb/pull/2592): Should return an error if user attempts to group by a field. - [#2499](https://github.com/influxdb/influxdb/pull/2499): Issuing a select query with tag as a values causes panic. +- [#2612](https://github.com/influxdb/influxdb/pull/2612): Query planner should validate distinct is passed a field. ## PRs - [#2569](https://github.com/influxdb/influxdb/pull/2569): Add derivative functions diff --git a/cmd/influxd/server_integration_test.go b/cmd/influxd/server_integration_test.go index 4784efce3b..09f082708d 100644 --- a/cmd/influxd/server_integration_test.go +++ b/cmd/influxd/server_integration_test.go @@ -705,29 +705,59 @@ func runTestsData(t *testing.T, testName string, nodes Cluster, database, retent }, { reset: true, - name: "distincts", + name: "distinct as call", write: `{"database" : "%DB%", "retentionPolicy" : "%RP%", "points": [ - {"name": "cpu", "time": "2000-01-01T00:00:00Z", "fields": {"value": 30}}, - {"name": "cpu", "time": "2000-01-01T00:00:10Z", "fields": {"value": 20}}, - {"name": "cpu", "time": "2000-01-01T00:00:20Z", "fields": {"value": 30}}, - {"name": "cpu", "time": "2000-01-01T00:00:30Z", "fields": {"value": 100}} + {"name": "cpu", "time": "2000-01-01T00:00:00Z", "tags": {"host": "server01"}, "fields": {"value": 30}}, + {"name": "cpu", "time": "2000-01-01T00:00:10Z", "tags": {"host": "server02"}, "fields": {"value": 20}}, + {"name": "cpu", "time": "2000-01-01T00:00:20Z", "tags": {"host": "server03"}, "fields": {"value": 30}}, + {"name": "cpu", "time": "2000-01-01T00:00:30Z", "tags": {"host": "server03"}, "fields": {"value": 100}} ]}`, query: `SELECT distinct(value) FROM cpu`, queryDb: "%DB%", expected: `{"results":[{"series":[{"name":"cpu","columns":["time","distinct"],"values":[["1970-01-01T00:00:00Z",[20,30,100]]]}]}]}`, }, { - name: "distincts alt syntax", + name: "distinct alt syntax", query: `SELECT distinct value FROM cpu`, queryDb: "%DB%", expected: `{"results":[{"series":[{"name":"cpu","columns":["time","distinct"],"values":[["1970-01-01T00:00:00Z",[20,30,100]]]}]}]}`, }, + { + name: "distinct select tag", + query: `SELECT distinct(host) FROM cpu`, + queryDb: "%DB%", + expected: `{"results":[{"error":"host isn't a field on measurement cpu; to query the unique values for a tag use SHOW TAG VALUES FROM cpu WITH KEY = \"host\""}]}`, + }, + { + name: "distinct alt select tag", + query: `SELECT distinct host FROM cpu`, + queryDb: "%DB%", + expected: `{"results":[{"error":"host isn't a field on measurement cpu; to query the unique values for a tag use SHOW TAG VALUES FROM cpu WITH KEY = \"host\""}]}`, + }, { name: "count distinct", query: `SELECT count(distinct value) FROM cpu`, queryDb: "%DB%", expected: `{"results":[{"series":[{"name":"cpu","columns":["time","count"],"values":[["1970-01-01T00:00:00Z",3]]}]}]}`, }, + { + name: "count distinct as call", + query: `SELECT count(distinct(value)) FROM cpu`, + queryDb: "%DB%", + expected: `{"results":[{"series":[{"name":"cpu","columns":["time","count"],"values":[["1970-01-01T00:00:00Z",3]]}]}]}`, + }, + { + name: "count distinct select tag", + query: `SELECT count(distinct host) FROM cpu`, + queryDb: "%DB%", + expected: `{"results":[{"error":"host isn't a field on measurement cpu; count(distinct) on tags isn't yet supported"}]}`, + }, + { + name: "count distinct as call select tag", + query: `SELECT count(distinct(host)) FROM cpu`, + queryDb: "%DB%", + expected: `{"results":[{"error":"host isn't a field on measurement cpu; count(distinct) on tags isn't yet supported"}]}`, + }, { reset: true, name: "aggregations", diff --git a/influxql/engine.go b/influxql/engine.go index 652e40b049..ad760d0ea8 100644 --- a/influxql/engine.go +++ b/influxql/engine.go @@ -702,7 +702,7 @@ func (m *MapReduceJob) processRawResults(values []*rawQueryMapOutput) *Row { selectNames[0], selectNames[i] = selectNames[i], selectNames[0] } hasTime = true - break + break } } diff --git a/tx.go b/tx.go index 16c679f7f4..892b3bd0e8 100644 --- a/tx.go +++ b/tx.go @@ -374,6 +374,8 @@ func (l *LocalMapper) Begin(c *influxql.Call, startingTime int64, chunkSize int) l.chunkSize = chunkSize l.tmin = startingTime + var isCountDistinct bool + // determine if this is a raw data query with a single field, multiple fields, or an aggregate var fieldName string if c == nil { // its a raw data query @@ -400,17 +402,27 @@ func (l *LocalMapper) Begin(c *influxql.Call, startingTime int64, chunkSize int) if c.Name != "count" { return fmt.Errorf("aggregate call didn't contain a field %s", c.String()) } + isCountDistinct = true fieldName = lit.Val default: return fmt.Errorf("aggregate call didn't contain a field %s", c.String()) } + + isCountDistinct = isCountDistinct || (c.Name == "count" && nested.Name == "distinct") } // set up the field info if a specific field was set for this mapper if fieldName != "" { f := l.decoder.FieldByName(fieldName) if f == nil { - return fmt.Errorf("%s isn't a field on measurement %s", fieldName, l.job.MeasurementName) + switch { + case c.Name == "distinct": + return fmt.Errorf("%s isn't a field on measurement %s; to query the unique values for a tag use SHOW TAG VALUES FROM %s WITH KEY = %q", fieldName, l.job.MeasurementName, l.job.MeasurementName, fieldName) + case isCountDistinct: + return fmt.Errorf("%s isn't a field on measurement %s; count(distinct) on tags isn't yet supported", fieldName, l.job.MeasurementName) + default: + return fmt.Errorf("%s isn't a field on measurement %s", fieldName, l.job.MeasurementName) + } } l.fieldID = f.ID l.fieldName = f.Name