2018-05-15 20:11:32 +00:00
|
|
|
package influxql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2018-05-21 23:02:42 +00:00
|
|
|
"github.com/influxdata/platform/query"
|
2018-05-21 21:20:06 +00:00
|
|
|
"github.com/influxdata/platform/query/execute"
|
2018-05-15 20:11:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// MultiResultEncoder encodes results as InfluxQL JSON format.
|
|
|
|
type MultiResultEncoder struct{}
|
|
|
|
|
|
|
|
// Encode writes a collection of results to the influxdb 1.X http response format.
|
|
|
|
// Expectations/Assumptions:
|
|
|
|
// 1. Each result will be published as a 'statement' in the top-level list of results. The 'staementID'
|
|
|
|
// will be interpreted as an integer, and will return an error otherwise.
|
|
|
|
// 2. If the _field name is present in the tags, and a _value column is present, the _value column will
|
|
|
|
// be renamed to the value of the _field tag
|
|
|
|
// 3. If the _measurement name is present in the tags, it will be used as the row.Name for all rows.
|
|
|
|
// Otherwise, we'll use the column value, which _must_ be present in that case.
|
|
|
|
|
2018-05-21 23:02:42 +00:00
|
|
|
func (e *MultiResultEncoder) Encode(w io.Writer, results query.ResultIterator) error {
|
2018-05-15 20:11:32 +00:00
|
|
|
resp := Response{}
|
|
|
|
|
|
|
|
for results.More() {
|
|
|
|
name, r := results.Next()
|
2018-05-24 21:33:51 +00:00
|
|
|
id, err := strconv.Atoi(name)
|
2018-05-15 20:11:32 +00:00
|
|
|
if err != nil {
|
2018-05-24 21:33:51 +00:00
|
|
|
return fmt.Errorf("unable to parse statement id from result name: %s", err)
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
2018-05-24 21:33:51 +00:00
|
|
|
blocks := r.Blocks()
|
|
|
|
|
|
|
|
result := Result{StatementID: id}
|
2018-05-21 23:02:42 +00:00
|
|
|
err = blocks.Do(func(b query.Block) error {
|
2018-05-15 20:11:32 +00:00
|
|
|
r := NewRow()
|
|
|
|
|
|
|
|
fieldName := ""
|
|
|
|
measurementVaries := -1
|
2018-05-19 21:28:37 +00:00
|
|
|
for j, c := range b.Key().Cols() {
|
2018-05-21 23:02:42 +00:00
|
|
|
if c.Type != query.TString {
|
2018-05-19 21:28:37 +00:00
|
|
|
return fmt.Errorf("partition column %q is not a string type", c.Label)
|
|
|
|
}
|
|
|
|
v := b.Key().Value(j).(string)
|
|
|
|
if c.Label == "_measurement" {
|
2018-05-15 20:11:32 +00:00
|
|
|
r.Name = v
|
2018-05-19 21:28:37 +00:00
|
|
|
} else if c.Label == "_field" {
|
2018-05-15 20:11:32 +00:00
|
|
|
fieldName = v
|
|
|
|
} else {
|
2018-05-19 21:28:37 +00:00
|
|
|
r.Tags[c.Label] = v
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, c := range b.Cols() {
|
|
|
|
if c.Label == "_time" {
|
|
|
|
r.Columns = append(r.Columns, "time")
|
|
|
|
} else if c.Label == "_value" && fieldName != "" {
|
|
|
|
r.Columns = append(r.Columns, fieldName)
|
2018-05-19 21:28:37 +00:00
|
|
|
} else if !b.Key().HasCol(c.Label) {
|
2018-05-15 20:11:32 +00:00
|
|
|
r.Columns = append(r.Columns, c.Label)
|
|
|
|
if r.Name == "" && c.Label == "_measurement" {
|
|
|
|
measurementVaries = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if r.Name == "" && measurementVaries == -1 {
|
|
|
|
return fmt.Errorf("no Measurement name found in result blocks for result: %s", name)
|
|
|
|
}
|
|
|
|
|
2018-05-19 21:28:37 +00:00
|
|
|
timeIdx := execute.ColIdx(execute.DefaultTimeColLabel, b.Cols())
|
|
|
|
if timeIdx < 0 {
|
|
|
|
return errors.New("table must have an _time column")
|
|
|
|
}
|
2018-05-21 23:02:42 +00:00
|
|
|
if typ := b.Cols()[timeIdx].Type; typ != query.TTime {
|
2018-05-19 21:28:37 +00:00
|
|
|
return fmt.Errorf("column _time must be of type Time got %v", typ)
|
|
|
|
}
|
2018-05-21 23:02:42 +00:00
|
|
|
err := b.Do(func(cr query.ColReader) error {
|
2018-05-19 21:28:37 +00:00
|
|
|
ts := cr.Times(timeIdx)
|
2018-05-15 20:11:32 +00:00
|
|
|
for i := range ts {
|
|
|
|
var v []interface{}
|
|
|
|
|
2018-05-19 21:28:37 +00:00
|
|
|
for j, c := range cr.Cols() {
|
|
|
|
if cr.Key().HasCol(c.Label) {
|
2018-05-15 20:11:32 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if j == measurementVaries {
|
2018-05-21 23:02:42 +00:00
|
|
|
if c.Type != query.TString {
|
2018-05-19 21:28:37 +00:00
|
|
|
return errors.New("unexpected type, _measurement is not a string")
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
2018-05-19 21:28:37 +00:00
|
|
|
r.Name = cr.Strings(j)[i]
|
2018-05-15 20:11:32 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch c.Type {
|
2018-05-21 23:02:42 +00:00
|
|
|
case query.TFloat:
|
2018-05-19 21:28:37 +00:00
|
|
|
v = append(v, cr.Floats(j)[i])
|
2018-05-21 23:02:42 +00:00
|
|
|
case query.TInt:
|
2018-05-19 21:28:37 +00:00
|
|
|
v = append(v, cr.Ints(j)[i])
|
2018-05-21 23:02:42 +00:00
|
|
|
case query.TString:
|
2018-05-19 21:28:37 +00:00
|
|
|
v = append(v, cr.Strings(j)[i])
|
2018-05-21 23:02:42 +00:00
|
|
|
case query.TUInt:
|
2018-05-19 21:28:37 +00:00
|
|
|
v = append(v, cr.UInts(j)[i])
|
2018-05-21 23:02:42 +00:00
|
|
|
case query.TBool:
|
2018-05-19 21:28:37 +00:00
|
|
|
v = append(v, cr.Bools(j)[i])
|
2018-05-21 23:02:42 +00:00
|
|
|
case query.TTime:
|
2018-05-19 21:28:37 +00:00
|
|
|
v = append(v, cr.Times(j)[i].Time().Format(time.RFC3339))
|
2018-05-15 20:11:32 +00:00
|
|
|
default:
|
|
|
|
v = append(v, "unknown")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Values = append(r.Values, v)
|
|
|
|
}
|
2018-05-19 21:28:37 +00:00
|
|
|
return nil
|
2018-05-15 20:11:32 +00:00
|
|
|
})
|
2018-05-19 21:28:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
result.Series = append(result.Series, r)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error iterating through results: %s", err)
|
|
|
|
}
|
|
|
|
resp.Results = append(resp.Results, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.NewEncoder(w).Encode(resp)
|
|
|
|
}
|
|
|
|
func NewMultiResultEncoder() *MultiResultEncoder {
|
|
|
|
return new(MultiResultEncoder)
|
|
|
|
}
|