diff --git a/pkg/tsdb/influxdb/models.go b/pkg/tsdb/influxdb/models.go index 00a718e9b1e..a70fe7f5d47 100644 --- a/pkg/tsdb/influxdb/models.go +++ b/pkg/tsdb/influxdb/models.go @@ -15,11 +15,6 @@ type Tag struct { Value string } -type QueryPart struct { - Type string - Params []string -} - type Select []QueryPart type InfluxDbSelect struct { diff --git a/pkg/tsdb/influxdb/parser_test.go b/pkg/tsdb/influxdb/parser_test.go index cb7c27ec35f..19e2c228f1a 100644 --- a/pkg/tsdb/influxdb/parser_test.go +++ b/pkg/tsdb/influxdb/parser_test.go @@ -14,70 +14,84 @@ func TestInfluxdbQueryParser(t *testing.T) { Convey("converting metric name", func() { json := ` - { - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "$interval" - ], - "type": "time" - }, - { - "type": "tag", - "params": [ - "datacenter" - ] - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "logins.count", - "policy": "default", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "value" + { + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "datacenter" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } ], - "type": "field" - }, - { - "params": [ - + "measurement": "logins.count", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "type": "field", + "params": [ + "value" + ] + }, + { + "type": "count", + "params": [] + } + ], + [ + { + "type": "field", + "params": [ + "value" + ] + }, + { + "type": "mean", + "params": [] + } + ], + [ + { + "type": "field", + "params": [ + "value" + ] + }, + { + "type": "mean", + "params": [] + }, + { + "type": "math", + "params": [ + " / 100" + ] + } + ] ], - "type": "count" + "tags": [ + { + "key": "datacenter", + "operator": "=", + "value": "America" + } + ] } - ], - [ - { - "params": [ - "value" - ], - "type": "field" - }, - { - "params": [ - - ], - "type": "mean" - } - ] - ], - "tags": [ - { - "key": "datacenter", - "operator": "=", - "value": "America" - } - ] - } ` modelJson, err := simplejson.NewJson([]byte(json)) @@ -86,7 +100,7 @@ func TestInfluxdbQueryParser(t *testing.T) { res, err := parser.Parse(modelJson) So(err, ShouldBeNil) So(len(res.GroupBy), ShouldEqual, 3) - So(len(res.Selects), ShouldEqual, 2) + So(len(res.Selects), ShouldEqual, 3) So(len(res.Tags), ShouldEqual, 1) }) }) diff --git a/pkg/tsdb/influxdb/query_part.go b/pkg/tsdb/influxdb/query_part.go index 3d5a51d42c0..cba0c57b6b1 100644 --- a/pkg/tsdb/influxdb/query_part.go +++ b/pkg/tsdb/influxdb/query_part.go @@ -1,5 +1,124 @@ package influxdb -type Selector interface { - Render(input string) string +import ( + "fmt" + "strings" +) + +var renders map[string]QueryDefinition + +type QueryDefinition struct { + Renderer func(part *QueryPart, innerExpr string) string +} + +func init() { + renders = make(map[string]QueryDefinition) + + renders["field"] = QueryDefinition{Renderer: fieldRenderer} + + renders["spread"] = QueryDefinition{Renderer: functionRenderer} + renders["count"] = QueryDefinition{Renderer: functionRenderer} + renders["distinct"] = QueryDefinition{Renderer: functionRenderer} + renders["integral"] = QueryDefinition{Renderer: functionRenderer} + renders["mean"] = QueryDefinition{Renderer: functionRenderer} + renders["median"] = QueryDefinition{Renderer: functionRenderer} + renders["sum"] = QueryDefinition{Renderer: functionRenderer} + + renders["derivative"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']}], + } + + renders["non_negative_derivative"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']}], + } + renders["difference"] = QueryDefinition{Renderer: functionRenderer} + renders["moving_average"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{ name: "window", type: "number", options: [5, 10, 20, 30, 40]}] + } + renders["stddev"] = QueryDefinition{Renderer: functionRenderer} + renders["time"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{ name: "interval", type: "time", options: ['auto', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }], + } + renders["fill"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous'] }], + } + renders["elapsed"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']}], + } + renders["bottom"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{name: 'count', type: 'int'}], + } + + renders["first"] = QueryDefinition{Renderer: functionRenderer} + renders["last"] = QueryDefinition{Renderer: functionRenderer} + renders["max"] = QueryDefinition{Renderer: functionRenderer} + renders["min"] = QueryDefinition{Renderer: functionRenderer} + renders["percentile"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{name: 'nth', type: 'int'}], + } + renders["top"] = QueryDefinition{ + Renderer: functionRenderer, + //params: [{name: 'count', type: 'int'}], + } + renders["tag"] = QueryDefinition{ + Renderer: fieldRenderer, + //params: [{name: 'tag', type: 'string', dynamicLookup: true}], + } + + renders["math"] = QueryDefinition{Renderer: suffixRenderer} + renders["alias"] = QueryDefinition{Renderer: aliasRenderer} +} + +func fieldRenderer(part *QueryPart, innerExpr string) string { + if part.Params[0] == "*" { + return "*" + } + return fmt.Sprintf(`"%v"`, part.Params[0]) +} + +func functionRenderer(part *QueryPart, innerExpr string) string { + params := strings.Join(part.Params, ", ") + + if len(part.Params) > 0 { + return fmt.Sprintf("%s(%s, %s)", part.Type, innerExpr, params) + } + + return fmt.Sprintf("%s(%s)", part.Type, innerExpr) +} + +func suffixRenderer(part *QueryPart, innerExpr string) string { + return fmt.Sprintf("%s %s", innerExpr, part.Params[0]) +} + +func aliasRenderer(part *QueryPart, innerExpr string) string { + return fmt.Sprintf(`%s AS "%s"`, innerExpr, part.Params[0]) +} + +func (r QueryDefinition) Render(part *QueryPart, innerExpr string) string { + return r.Renderer(part, innerExpr) +} + +type QueryPartDefinition struct { +} + +type QueryPart struct { + Type string + Params []string +} + +func (qp *QueryPart) Render(expr string) (string, error) { + renderFn, exist := renders[qp.Type] + if !exist { + return "", fmt.Errorf("could not find render strategy %s", qp.Type) + } + + return renderFn.Renderer(qp, expr), nil } diff --git a/pkg/tsdb/influxdb/query_part_test.go b/pkg/tsdb/influxdb/query_part_test.go index 20f12cc3359..eb2583e7711 100644 --- a/pkg/tsdb/influxdb/query_part_test.go +++ b/pkg/tsdb/influxdb/query_part_test.go @@ -9,55 +9,54 @@ import ( func TestInfluxdbQueryPart(t *testing.T) { Convey("Influxdb query part builder", t, func() { - Convey("can build query", func() { + Convey("should handle field renderer parts", func() { + part := QueryPart{ + Type: "field", + Params: []string{"value"}, + } + + res, _ := part.Render("value") + So(res, ShouldEqual, `"value"`) }) - Convey("empty queries should return error", func() { + Convey("should handle nested function parts", func() { + part := QueryPart{ + Type: "derivative", + Params: []string{"10s"}, + } + res, _ := part.Render("mean(value)") + So(res, ShouldEqual, "derivative(mean(value), 10s)") + }) + + Convey("should nest spread function", func() { + part := QueryPart{ + Type: "spread", + } + + res, err := part.Render("value") + So(err, ShouldBeNil) + So(res, ShouldEqual, "spread(value)") + }) + + Convey("should handle suffix parts", func() { + part := QueryPart{ + Type: "math", + Params: []string{"/ 100"}, + } + + res, _ := part.Render("mean(value)") + So(res, ShouldEqual, "mean(value) / 100") + }) + + Convey("should handle alias parts", func() { + part := QueryPart{ + Type: "alias", + Params: []string{"test"}, + } + + res, _ := part.Render("mean(value)") + So(res, ShouldEqual, `mean(value) AS "test"`) }) }) } - -/* - describe('series with mesurement only', () => { - it('should handle nested function parts', () => { - var part = queryPart.create({ - type: 'derivative', - params: ['10s'], - }); - - expect(part.text).to.be('derivative(10s)'); - expect(part.render('mean(value)')).to.be('derivative(mean(value), 10s)'); - }); - - it('should nest spread function', () => { - var part = queryPart.create({ - type: 'spread' - }); - - expect(part.text).to.be('spread()'); - expect(part.render('value')).to.be('spread(value)'); - }); - - it('should handle suffirx parts', () => { - var part = queryPart.create({ - type: 'math', - params: ['/ 100'], - }); - - expect(part.text).to.be('math(/ 100)'); - expect(part.render('mean(value)')).to.be('mean(value) / 100'); - }); - - it('should handle alias parts', () => { - var part = queryPart.create({ - type: 'alias', - params: ['test'], - }); - - expect(part.text).to.be('alias(test)'); - expect(part.render('mean(value)')).to.be('mean(value) AS "test"'); - }); - - }); -*/