diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b9371b4f8a..b755388a832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#3529](https://github.com/influxdb/influxdb/pull/3529): Add TLS support for OpenTSDB plugin. Thanks @nathanielc - [#3421](https://github.com/influxdb/influxdb/issues/3421): Should update metastore and cluster if IP or hostname changes - [#3502](https://github.com/influxdb/influxdb/pull/3502): Importer for 0.8.9 data via the CLI +- [#3564](https://github.com/influxdb/influxdb/pull/3564): Fix alias, maintain column sort order ### Bugfixes - [#3405](https://github.com/influxdb/influxdb/pull/3405): Prevent database panic when fields are missing. Thanks @jhorwit2 @@ -22,6 +23,7 @@ - [#3545](https://github.com/influxdb/influxdb/issues/3545): Fix parsing string fields with newlines - [#3579](https://github.com/influxdb/influxdb/issues/3579): Revert breaking change to `client.NewClient` function - [#3580](https://github.com/influxdb/influxdb/issues/3580): Do not allow wildcards with fields in select statements +- [#3530](https://github.com/influxdb/influxdb/pull/3530): Aliasing a column no longer works ## v0.9.2 [2015-07-24] diff --git a/cmd/influxd/run/server_test.go b/cmd/influxd/run/server_test.go index 67e3e22010a..2bf6b9e9f08 100644 --- a/cmd/influxd/run/server_test.go +++ b/cmd/influxd/run/server_test.go @@ -908,7 +908,7 @@ func TestServer_Query_Tags(t *testing.T) { &Query{ name: "field with two tags should succeed", command: `SELECT host, value, core FROM db0.rp0.cpu`, - exp: fmt.Sprintf(`{"results":[{"series":[{"name":"cpu","tags":{"host":"server01"},"columns":["time","core","value"],"values":[["%s",4,100]]},{"name":"cpu","tags":{"host":"server02"},"columns":["time","core","value"],"values":[["%s",2,50]]}]}]}`, now.Format(time.RFC3339Nano), now.Add(1).Format(time.RFC3339Nano)), + exp: fmt.Sprintf(`{"results":[{"series":[{"name":"cpu","tags":{"host":"server01"},"columns":["time","value","core"],"values":[["%s",100,4]]},{"name":"cpu","tags":{"host":"server02"},"columns":["time","value","core"],"values":[["%s",50,2]]}]}]}`, now.Format(time.RFC3339Nano), now.Add(1).Format(time.RFC3339Nano)), }, &Query{ name: "select * with tags should succeed", @@ -1014,6 +1014,78 @@ func TestServer_Query_Tags(t *testing.T) { } } +// Ensure the server correctly queries with an alias. +func TestServer_Query_Alias(t *testing.T) { + t.Parallel() + s := OpenServer(NewConfig(), "") + defer s.Close() + + if err := s.CreateDatabaseAndRetentionPolicy("db0", newRetentionPolicyInfo("rp0", 1, 1*time.Hour)); err != nil { + t.Fatal(err) + } + + writes := []string{ + fmt.Sprintf("cpu value=1i,steps=3i %d", mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano()), + fmt.Sprintf("cpu value=2i,steps=4i %d", mustParseTime(time.RFC3339Nano, "2000-01-01T00:01:00Z").UnixNano()), + } + test := NewTest("db0", "rp0") + test.write = strings.Join(writes, "\n") + + test.addQueries([]*Query{ + &Query{ + name: "baseline query - SELECT * FROM db0.rp0.cpu", + command: `SELECT * FROM db0.rp0.cpu`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","steps","value"],"values":[["2000-01-01T00:00:00Z",3,1],["2000-01-01T00:01:00Z",4,2]]}]}]}`, + }, + &Query{ + name: "basic query with alias - SELECT steps, value as v FROM db0.rp0.cpu", + command: `SELECT steps, value as v FROM db0.rp0.cpu`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","steps","v"],"values":[["2000-01-01T00:00:00Z",3,1],["2000-01-01T00:01:00Z",4,2]]}]}]}`, + }, + &Query{ + name: "double aggregate sum - SELECT sum(value), sum(steps) FROM db0.rp0.cpu", + command: `SELECT sum(value), sum(steps) FROM db0.rp0.cpu`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","sum","sum"],"values":[["1970-01-01T00:00:00Z",3,7]]}]}]}`, + }, + &Query{ + name: "double aggregate sum reverse order - SELECT sum(steps), sum(value) FROM db0.rp0.cpu", + command: `SELECT sum(steps), sum(value) FROM db0.rp0.cpu`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","sum","sum"],"values":[["1970-01-01T00:00:00Z",7,3]]}]}]}`, + }, + &Query{ + name: "double aggregate sum with alias - SELECT sum(value) as sumv, sum(steps) as sums FROM db0.rp0.cpu", + command: `SELECT sum(value) as sumv, sum(steps) as sums FROM db0.rp0.cpu`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","sumv","sums"],"values":[["1970-01-01T00:00:00Z",3,7]]}]}]}`, + }, + &Query{ + name: "double aggregate with same value - SELECT sum(value), mean(value) FROM db0.rp0.cpu", + command: `SELECT sum(value), mean(value) FROM db0.rp0.cpu`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","sum","mean"],"values":[["1970-01-01T00:00:00Z",3,1.5]]}]}]}`, + }, + &Query{ + name: "double aggregate with same value and same alias - SELECT mean(value) as mv, max(value) as mv FROM db0.rp0.cpu", + command: `SELECT mean(value) as mv, max(value) as mv FROM db0.rp0.cpu`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","mv","mv"],"values":[["1970-01-01T00:00:00Z",1.5,2]]}]}]}`, + }, + }...) + + if err := test.init(s); err != nil { + t.Fatalf("test init failed: %s", err) + } + + for _, query := range test.queries { + 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()) + } + } +} + // Ensure the server will succeed and error for common scenarios. func TestServer_Query_Common(t *testing.T) { t.Parallel() @@ -1226,7 +1298,7 @@ func TestServer_Query_SelectRawCalculus(t *testing.T) { &Query{ name: "calculate single derivate", command: `SELECT derivative(value) from db0.rp0.cpu`, - exp: `{"results":[{"series":[{"name":"cpu","columns":["time","value"],"values":[["2010-07-01T18:47:02Z",-200]]}]}]}`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","derivative"],"values":[["2010-07-01T18:47:02Z",-200]]}]}]}`, }, }...) @@ -2134,7 +2206,7 @@ func TestServer_Query_Where_Fields(t *testing.T) { name: "string AND query, all fields in SELECT", params: url.Values{"db": []string{"db0"}}, command: `SELECT alert_id,tenant_id,_cust FROM cpu WHERE alert_id='alert' AND tenant_id='tenant'`, - exp: `{"results":[{"series":[{"name":"cpu","columns":["time","_cust","alert_id","tenant_id"],"values":[["2015-02-28T01:03:36.703820946Z","johnson brothers","alert","tenant"]]}]}]}`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","alert_id","tenant_id","_cust"],"values":[["2015-02-28T01:03:36.703820946Z","alert","tenant","johnson brothers"]]}]}]}`, }, &Query{ name: "string AND query, all fields in SELECT, one in parenthesis", @@ -3086,7 +3158,7 @@ func TestServer_Query_EvilIdentifiers(t *testing.T) { &Query{ name: `query evil identifiers`, command: `SELECT "select", "in-bytes" FROM cpu`, - exp: `{"results":[{"series":[{"name":"cpu","columns":["time","in-bytes","select"],"values":[["2000-01-01T00:00:00Z",2,1]]}]}]}`, + exp: `{"results":[{"series":[{"name":"cpu","columns":["time","select","in-bytes"],"values":[["2000-01-01T00:00:00Z",1,2]]}]}]}`, params: url.Values{"db": []string{"db0"}}, }, }...) diff --git a/influxql/ast.go b/influxql/ast.go index 089b66e4bb3..7b41ac1cb24 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -1964,6 +1964,32 @@ func (s *ShowFieldKeysStatement) RequiredPrivileges() ExecutionPrivileges { // Fields represents a list of fields. type Fields []*Field +// AliasNames returns a list of calculated field names in +// order of alias, function name, then field. +func (a Fields) AliasNames() []string { + names := []string{} + for _, f := range a { + names = append(names, f.Name()) + } + return names +} + +// Names returns a list of raw field names. +func (a Fields) Names() []string { + names := []string{} + for _, f := range a { + var name string + switch expr := f.Expr.(type) { + case *Call: + name = expr.Name + case *VarRef: + name = expr.Val + } + names = append(names, name) + } + return names +} + // String returns a string representation of the fields. func (a Fields) String() string { var str []string diff --git a/tsdb/executor.go b/tsdb/executor.go index eb76157d75a..f7b9d7bf8ed 100644 --- a/tsdb/executor.go +++ b/tsdb/executor.go @@ -157,10 +157,18 @@ func (e *Executor) executeRaw(out chan *influxql.Row) { } } - // Get the union of SELECT fields across all mappers. - selectFields := newStringSet() - for _, m := range e.mappers { - selectFields.add(m.Fields()...) + // Get the distinct fields across all mappers. + var selectFields, aliasFields []string + if e.stmt.HasWildcard() { + sf := newStringSet() + for _, m := range e.mappers { + sf.add(m.Fields()...) + } + selectFields = sf.list() + aliasFields = selectFields + } else { + selectFields = e.stmt.Fields.Names() + aliasFields = e.stmt.Fields.AliasNames() } // Used to read ahead chunks from mappers. @@ -290,7 +298,8 @@ func (e *Executor) executeRaw(out chan *influxql.Row) { chunkSize: e.chunkSize, name: chunkedOutput.Name, tags: chunkedOutput.Tags, - selectNames: selectFields.list(), + selectNames: selectFields, + aliasNames: aliasFields, fields: e.stmt.Fields, c: out, } @@ -562,8 +571,9 @@ type limitedRowWriter struct { offset int name string tags map[string]string - selectNames []string fields influxql.Fields + selectNames []string + aliasNames []string c chan *influxql.Row currValues []*MapperValue @@ -658,6 +668,7 @@ func (r *limitedRowWriter) processValues(values []*MapperValue) *influxql.Row { }() selectNames := r.selectNames + aliasNames := r.aliasNames if r.transformer != nil { values = r.transformer.Process(values) @@ -679,21 +690,24 @@ func (r *limitedRowWriter) processValues(values []*MapperValue) *influxql.Row { // time should always be in the list of names they get back if !hasTime { selectNames = append([]string{"time"}, selectNames...) + aliasNames = append([]string{"time"}, aliasNames...) } // since selectNames can contain tags, we need to strip them out selectFields := make([]string, 0, len(selectNames)) + aliasFields := make([]string, 0, len(selectNames)) - for _, n := range selectNames { + for i, n := range selectNames { if _, found := r.tags[n]; !found { selectFields = append(selectFields, n) + aliasFields = append(aliasFields, aliasNames[i]) } } row := &influxql.Row{ Name: r.name, Tags: r.tags, - Columns: selectFields, + Columns: aliasFields, } // Kick out an empty row it no results available. @@ -710,7 +724,12 @@ func (r *limitedRowWriter) processValues(values []*MapperValue) *influxql.Row { if singleValue { vals[0] = time.Unix(0, v.Time).UTC() - vals[1] = v.Value.(interface{}) + switch val := v.Value.(type) { + case map[string]interface{}: + vals[1] = val[selectFields[1]] + default: + vals[1] = v.Value.(interface{}) + } } else { fields := v.Value.(map[string]interface{})