Ensure column names get implicitly renamed with conflicts

pull/5742/head
Jonathan A. Sternberg 2016-02-18 14:15:08 -05:00
parent 8fc6a0f648
commit a8d637b03c
3 changed files with 110 additions and 25 deletions

View File

@ -1296,12 +1296,12 @@ func TestServer_Query_Alias(t *testing.T) {
&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]]}]}]}`,
exp: `{"results":[{"series":[{"name":"cpu","columns":["time","sum","sum_1"],"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]]}]}]}`,
exp: `{"results":[{"series":[{"name":"cpu","columns":["time","sum","sum_1"],"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",
@ -3805,13 +3805,13 @@ func TestServer_Query_Wildcards(t *testing.T) {
name: "wildcard and field in select",
params: url.Values{"db": []string{"db0"}},
command: `SELECT value, * FROM wildcard`,
exp: `{"results":[{"series":[{"name":"wildcard","columns":["time","value","region","value","valx"],"values":[["2000-01-01T00:00:00Z",10,"us-east",10,null],["2000-01-01T00:00:10Z",null,"us-east",null,20],["2000-01-01T00:00:20Z",30,"us-east",30,40]]}]}]}`,
exp: `{"results":[{"series":[{"name":"wildcard","columns":["time","value","region","value_1","valx"],"values":[["2000-01-01T00:00:00Z",10,"us-east",10,null],["2000-01-01T00:00:10Z",null,"us-east",null,20],["2000-01-01T00:00:20Z",30,"us-east",30,40]]}]}]}`,
},
&Query{
name: "field and wildcard in select",
params: url.Values{"db": []string{"db0"}},
command: `SELECT value, * FROM wildcard`,
exp: `{"results":[{"series":[{"name":"wildcard","columns":["time","value","region","value","valx"],"values":[["2000-01-01T00:00:00Z",10,"us-east",10,null],["2000-01-01T00:00:10Z",null,"us-east",null,20],["2000-01-01T00:00:20Z",30,"us-east",30,40]]}]}]}`,
exp: `{"results":[{"series":[{"name":"wildcard","columns":["time","value","region","value_1","valx"],"values":[["2000-01-01T00:00:00Z",10,"us-east",10,null],["2000-01-01T00:00:10Z",null,"us-east",null,20],["2000-01-01T00:00:20Z",30,"us-east",30,40]]}]}]}`,
},
&Query{
name: "field and wildcard in group by",

View File

@ -9,8 +9,6 @@ import (
"strconv"
"strings"
"time"
"github.com/influxdata/influxdb/pkg/slices"
)
// DataType represents the primitive data types available in InfluxQL.
@ -1036,34 +1034,70 @@ func (s *SelectStatement) RewriteTimeFields() {
// ColumnNames will walk all fields and functions and return the appropriate field names for the select statement
// while maintaining order of the field names
func (s *SelectStatement) ColumnNames() []string {
columnNames := []string{}
if !s.OmitTime {
columnNames = append(columnNames, "time")
}
// First walk each field
// First walk each field to determine the number of columns.
columnFields := Fields{}
for _, field := range s.Fields {
columnFields = append(columnFields, field)
switch f := field.Expr.(type) {
case *Call:
if f.Name == "top" || f.Name == "bottom" {
if len(f.Args) == 2 {
columnNames = append(columnNames, f.Name)
continue
for _, arg := range f.Args[1:] {
ref, ok := arg.(*VarRef)
if ok {
columnFields = append(columnFields, &Field{Expr: ref})
}
}
// We have a special case now where we have to add the column names for the fields TOP or BOTTOM asked for as well
columnNames = slices.Union(columnNames, f.Fields(), true)
continue
}
columnNames = append(columnNames, field.Name())
default:
// time is always first, and we already added it, so ignore it if they asked for it anywhere else.
if field.Name() != "time" {
columnNames = append(columnNames, field.Name())
}
}
}
// Determine if we should add an extra column for an implicit time.
offset := 0
if !s.OmitTime {
offset++
}
columnNames := make([]string, len(columnFields)+offset)
if !s.OmitTime {
// Add the implicit time if requested.
columnNames[0] = "time"
}
// Keep track of the encountered column names.
names := make(map[string]int)
// Resolve aliases first.
for i, col := range columnFields {
if col.Alias != "" {
columnNames[i+offset] = col.Alias
names[col.Alias] = 1
}
}
// Resolve any generated names and resolve conflicts.
for i, col := range columnFields {
if columnNames[i+offset] != "" {
continue
}
name := col.Name()
count, conflict := names[name]
if conflict {
for {
resolvedName := fmt.Sprintf("%s_%d", name, count)
_, conflict = names[resolvedName]
if !conflict {
names[name] = count + 1
name = resolvedName
break
}
count++
}
}
names[name] += 1
columnNames[i+offset] = name
}
return columnNames
}

View File

@ -1158,6 +1158,57 @@ func Test_fieldsNames(t *testing.T) {
}
func TestSelect_ColumnNames(t *testing.T) {
for i, tt := range []struct {
stmt *influxql.SelectStatement
columns []string
}{
{
stmt: &influxql.SelectStatement{
Fields: influxql.Fields([]*influxql.Field{
{Expr: &influxql.VarRef{Val: "value"}},
}),
},
columns: []string{"time", "value"},
},
{
stmt: &influxql.SelectStatement{
Fields: influxql.Fields([]*influxql.Field{
{Expr: &influxql.VarRef{Val: "value"}},
{Expr: &influxql.VarRef{Val: "value"}},
{Expr: &influxql.VarRef{Val: "value_1"}},
}),
},
columns: []string{"time", "value", "value_1", "value_1_1"},
},
{
stmt: &influxql.SelectStatement{
Fields: influxql.Fields([]*influxql.Field{
{Expr: &influxql.VarRef{Val: "value"}},
{Expr: &influxql.VarRef{Val: "value_1"}},
{Expr: &influxql.VarRef{Val: "value"}},
}),
},
columns: []string{"time", "value", "value_1", "value_2"},
},
{
stmt: &influxql.SelectStatement{
Fields: influxql.Fields([]*influxql.Field{
{Expr: &influxql.VarRef{Val: "value"}},
{Expr: &influxql.VarRef{Val: "total"}, Alias: "value"},
{Expr: &influxql.VarRef{Val: "value"}},
}),
},
columns: []string{"time", "value_1", "value", "value_2"},
},
} {
columns := tt.stmt.ColumnNames()
if !reflect.DeepEqual(columns, tt.columns) {
t.Errorf("%d. expected %s, got %s", i, tt.columns, columns)
}
}
}
func TestSources_Names(t *testing.T) {
sources := influxql.Sources([]influxql.Source{
&influxql.Measurement{