Skip to content

Commit 3080d5c

Browse files
Support Druid GroupBy queries (#52)
1 parent 59cfc07 commit 3080d5c

File tree

4 files changed

+141
-2
lines changed

4 files changed

+141
-2
lines changed

pkg/tsdb/druid/druid.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,12 @@ func (ds *Service) executeQuery(
486486
}
487487
resultFramer = &r
488488
case "groupBy":
489-
return nil, errors.New("not implemented")
489+
var r result.GroupByResult
490+
_, err := s.client.Query().Execute(q, &r, headers)
491+
if err != nil {
492+
return nil, fmt.Errorf("Query error: %w", err)
493+
}
494+
resultFramer = &r
490495
case "scan":
491496
q.(*druidquery.Scan).SetResultFormat("compactedList")
492497
return nil, errors.New("not implemented")

pkg/tsdb/druid/result/groupby.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package result
2+
3+
import (
4+
"sort"
5+
"time"
6+
7+
"github.com/grafana/grafana-plugin-sdk-go/data"
8+
)
9+
10+
type GroupByResult []GroupByRecord
11+
12+
// Frame returns data formatted as Grafana Frame.
13+
func (t *GroupByResult) Frame() *data.Frame {
14+
columns := t.Columns()
15+
fields := make([]*data.Field, len(columns))
16+
for i, column := range columns {
17+
labels := data.Labels{}
18+
fields[i] = data.NewField(column, labels, t.Values(column))
19+
}
20+
return data.NewFrame("", fields...)
21+
}
22+
23+
// Columns returns list of columns. It calls `Columns()` on first record. If
24+
// no records are available it returns nil.
25+
func (t *GroupByResult) Columns() []string {
26+
for _, r := range *t {
27+
return r.Columns()
28+
}
29+
return nil
30+
}
31+
32+
// Values returns all values for given column.
33+
func (t *GroupByResult) Values(column string) interface{} {
34+
if len(*t) == 0 {
35+
return nil
36+
}
37+
results := make([]interface{}, len(*t))
38+
for i, r := range *t {
39+
results[i] = r.Value(column)
40+
}
41+
return toTypedResults(results)
42+
}
43+
44+
type GroupByRecord struct {
45+
Timestamp time.Time `json:"timestamp"`
46+
Event map[string]interface{} `json:"event"`
47+
}
48+
49+
// Columns returns list of columns for given record.
50+
// The first column will always be "timestamp" followed by other columns sorted
51+
// alphabetically.
52+
func (t *GroupByRecord) Columns() []string {
53+
columns := make([]string, len(t.Event)+1)
54+
columns[0] = timestampColumn
55+
i := 1
56+
for c := range t.Event {
57+
columns[i] = c
58+
i++
59+
}
60+
sort.Strings(columns[1:])
61+
return columns
62+
}
63+
64+
// Value returns value for given column.
65+
func (t *GroupByRecord) Value(column string) interface{} {
66+
if column == timestampColumn {
67+
return t.Timestamp
68+
}
69+
v, ok := t.Event[column]
70+
if !ok {
71+
return nil
72+
}
73+
return v
74+
}

pkg/tsdb/druid/result/groupby_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package result
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
"time"
7+
8+
"github.com/grafana/grafana-plugin-sdk-go/data"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestGroupByResultUnmarshal(t *testing.T) {
13+
input := []byte(`[
14+
{
15+
"timestamp": "2022-10-14T08:08:10.000Z",
16+
"event": {
17+
"dog_count": 47,
18+
"dog_rate": 2.083,
19+
"dog_name": "foo"
20+
}
21+
},
22+
{
23+
"timestamp": "2022-10-14T08:08:11.000Z",
24+
"event": {
25+
"dog_count": 75,
26+
"dog_rate": 3.846,
27+
"dog_name": "bar"
28+
}
29+
}
30+
]`)
31+
32+
var res GroupByResult
33+
err := json.Unmarshal(input, &res)
34+
assert.Nil(t, err, "Failed to unmarshal response")
35+
assert.Equal(t, len(res), 2, "Wrong number of unmarshalled results")
36+
frame := res.Frame()
37+
assert.Equal(t, len(frame.Fields), 4, "Wrong number of framed fields")
38+
39+
assert.Equal(t, frame.Fields[0].Name, "timestamp")
40+
assert.Equal(t, frame.Fields[0].Type(), data.FieldTypeTime)
41+
assert.Equal(t, frame.Fields[0].Len(), 2)
42+
assert.Equal(t, frame.Fields[0].At(0), time.Time(time.Date(2022, time.October, 14, 8, 8, 10, 0, time.UTC)))
43+
assert.Equal(t, frame.Fields[0].At(1), time.Time(time.Date(2022, time.October, 14, 8, 8, 11, 0, time.UTC)))
44+
45+
assert.Equal(t, frame.Fields[1].Name, "dog_count")
46+
assert.Equal(t, frame.Fields[1].Type(), data.FieldTypeFloat64)
47+
assert.Equal(t, frame.Fields[1].At(0), float64(47))
48+
assert.Equal(t, frame.Fields[1].At(1), float64(75))
49+
50+
assert.Equal(t, frame.Fields[2].Name, "dog_name")
51+
assert.Equal(t, frame.Fields[2].Type(), data.FieldTypeString)
52+
assert.Equal(t, frame.Fields[2].At(0), "foo")
53+
assert.Equal(t, frame.Fields[2].At(1), "bar")
54+
55+
assert.Equal(t, frame.Fields[3].Name, "dog_rate")
56+
assert.Equal(t, frame.Fields[3].Type(), data.FieldTypeFloat64)
57+
assert.Equal(t, frame.Fields[3].At(0), float64(2.083))
58+
assert.Equal(t, frame.Fields[3].At(1), float64(3.846))
59+
60+
}

pkg/tsdb/druid/result/topn.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type TopNRecord struct {
4646
// Columns returns list of columns for given record.
4747
// It assumes that every map from Result has the same columns, so it gets
4848
// the list from first item.
49-
// The first column will always be "timestamp" followed by other columns sorter
49+
// The first column will always be "timestamp" followed by other columns sorted
5050
// alphabetically.
5151
func (t *TopNRecord) Columns() []string {
5252
for _, result := range t.Result {

0 commit comments

Comments
 (0)