console: use a less hacky way to pass context to build SQL templates

Instead of trying to embed that in the template, provide a list of
templates with their associated input contexts and join them with UNION
ALL.
This commit is contained in:
Vincent Bernat
2025-09-04 07:26:16 +02:00
parent d3448222fc
commit 74146e428d
7 changed files with 382 additions and 224 deletions

View File

@@ -5,7 +5,6 @@ package console
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"sort" "sort"
@@ -82,33 +81,17 @@ AND (engine LIKE '%MergeTree' OR engine = 'Distributed')
return nil return nil
} }
// finalizeQuery builds the finalized query. A single "context" // inputContext is the intermeidate context provided by the input handler.
// function is provided to return a `Context` struct with all the
// information needed.
func (c *Component) finalizeQuery(query string) string {
t := template.Must(template.New("query").
Funcs(template.FuncMap{
"context": c.contextFunc,
}).
Option("missingkey=error").
Parse(strings.TrimSpace(query)))
buf := bytes.NewBufferString("")
if err := t.Execute(buf, nil); err != nil {
c.r.Err(err).Str("query", query).Msg("invalid query")
panic(err)
}
return buf.String()
}
type inputContext struct { type inputContext struct {
Start time.Time `json:"start"` Start time.Time
End time.Time `json:"end"` End time.Time
StartForInterval *time.Time `json:"start-for-interval,omitempty"` StartForTableSelection *time.Time
MainTableRequired bool `json:"main-table-required,omitempty"` MainTableRequired bool
Points uint `json:"points"` Points uint
Units string `json:"units,omitempty"` Units string
} }
// context is the context to finalize the template.
type context struct { type context struct {
Table string Table string
Timefilter string Timefilter string
@@ -119,6 +102,12 @@ type context struct {
ToStartOfInterval func(string) string ToStartOfInterval func(string) string
} }
// templateQuery holds a template string and its associated input context.
type templateQuery struct {
Template string
Context inputContext
}
// templateEscape escapes `{{` and `}}` from a string. In fact, only // templateEscape escapes `{{` and `}}` from a string. In fact, only
// the opening tag needs to be escaped. // the opening tag needs to be escaped.
func templateEscape(input string) string { func templateEscape(input string) string {
@@ -133,22 +122,20 @@ func templateWhere(qf query.Filter) string {
return fmt.Sprintf(`{{ .Timefilter }} AND (%s)`, templateEscape(qf.Direct())) return fmt.Sprintf(`{{ .Timefilter }} AND (%s)`, templateEscape(qf.Direct()))
} }
// templateTable builds a template directive to select the right table // finalizeTemplateQueries builds the finalized queries from a list of templateQuery.
func templateContext(context inputContext) string { // Each template is processed with its associated context and combined with UNION ALL.
encoded, err := json.Marshal(context) func (c *Component) finalizeTemplateQueries(queries []templateQuery) string {
if err != nil { parts := make([]string, len(queries))
panic(err) for i, q := range queries {
parts[i] = c.finalizeTemplateQuery(q)
} }
return fmt.Sprintf("context `%s`", string(encoded)) return strings.Join(parts, "\nUNION ALL\n")
} }
func (c *Component) contextFunc(inputStr string) context { // finalizeTemplateQuery builds the finalized query for a single templateQuery
var input inputContext func (c *Component) finalizeTemplateQuery(query templateQuery) string {
if err := json.Unmarshal([]byte(inputStr), &input); err != nil { input := query.Context
panic(err) table, computedInterval, targetInterval := c.computeTableAndInterval(query.Context)
}
table, computedInterval, targetInterval := c.computeTableAndInterval(input)
// Make start/end match the computed interval (currently equal to the table resolution) // Make start/end match the computed interval (currently equal to the table resolution)
start := input.Start.Truncate(computedInterval) start := input.Start.Truncate(computedInterval)
@@ -195,7 +182,8 @@ func (c *Component) contextFunc(inputStr string) context {
} }
c.metrics.clickhouseQueries.WithLabelValues(table).Inc() c.metrics.clickhouseQueries.WithLabelValues(table).Inc()
return context{
context := context{
Table: table, Table: table,
Timefilter: timefilter, Timefilter: timefilter,
TimefilterStart: timefilterStart, TimefilterStart: timefilterStart,
@@ -211,6 +199,16 @@ func (c *Component) contextFunc(inputStr string) context {
diffOffset) diffOffset)
}, },
} }
t := template.Must(template.New("query").
Option("missingkey=error").
Parse(strings.TrimSpace(query.Template)))
buf := bytes.NewBufferString("")
if err := t.Execute(buf, context); err != nil {
c.r.Err(err).Str("query", query.Template).Msg("invalid query")
panic(err)
}
return buf.String()
} }
func (c *Component) computeTableAndInterval(input inputContext) (string, time.Duration, time.Duration) { func (c *Component) computeTableAndInterval(input inputContext) (string, time.Duration, time.Duration) {
@@ -223,8 +221,8 @@ func (c *Component) computeTableAndInterval(input inputContext) (string, time.Du
targetIntervalForTableSelection = time.Second targetIntervalForTableSelection = time.Second
} }
startForTableSelection := input.Start startForTableSelection := input.Start
if input.StartForInterval != nil { if input.StartForTableSelection != nil {
startForTableSelection = *input.StartForInterval startForTableSelection = *input.StartForTableSelection
} }
table, computedInterval := c.getBestTable(startForTableSelection, targetIntervalForTableSelection) table, computedInterval := c.getBestTable(startForTableSelection, targetIntervalForTableSelection)
return table, computedInterval, targetInterval return table, computedInterval, targetInterval

View File

@@ -4,7 +4,6 @@
package console package console
import ( import (
"fmt"
"testing" "testing"
"time" "time"
@@ -192,7 +191,7 @@ func TestFinalizeQuery(t *testing.T) {
Context: inputContext{ Context: inputContext{
Start: time.Date(2022, 3, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 3, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 3, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 3, 11, 15, 45, 10, 0, time.UTC),
StartForInterval: func() *time.Time { StartForTableSelection: func() *time.Time {
t := time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC) t := time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC)
return &t return &t
}(), }(),
@@ -294,10 +293,12 @@ func TestFinalizeQuery(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.Description, func(t *testing.T) { t.Run(tc.Description, func(t *testing.T) {
c.flowsTables = tc.Tables c.flowsTables = tc.Tables
got := c.finalizeQuery( got := c.finalizeTemplateQuery(templateQuery{
fmt.Sprintf(`{{ with %s }}%s{{ end }}`, templateContext(tc.Context), tc.Query)) Template: tc.Query,
Context: tc.Context,
})
if diff := helpers.Diff(got, tc.Expected); diff != "" { if diff := helpers.Diff(got, tc.Expected); diff != "" {
t.Fatalf("finalizeQuery(): (-got, +want):\n%s", diff) t.Fatalf("finalizeTemplateQuery(): (-got, +want):\n%s", diff)
} }
}) })
} }

View File

@@ -100,7 +100,7 @@ type toSQL1Options struct {
mainTableRequired bool mainTableRequired bool
} }
func (input graphLineHandlerInput) toSQL1(axis int, options toSQL1Options) string { func (input graphLineHandlerInput) toSQL1(axis int, options toSQL1Options) templateQuery {
var startForInterval *time.Time var startForInterval *time.Time
var offsetShift string var offsetShift string
if !options.offsetedStart.IsZero() { if !options.offsetedStart.IsZero() {
@@ -159,8 +159,7 @@ func (input graphLineHandlerInput) toSQL1(axis int, options toSQL1Options) strin
} }
} }
sqlQuery := fmt.Sprintf(` template := fmt.Sprintf(`%s
{{ with %s }}%s
SELECT %d AS axis, * FROM ( SELECT %d AS axis, * FROM (
SELECT SELECT
%s %s
@@ -171,54 +170,58 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }}%s FROM {{ .TimefilterStart }}%s
TO {{ .TimefilterEnd }} + INTERVAL 1 second%s TO {{ .TimefilterEnd }} + INTERVAL 1 second%s
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS %s)) INTERPOLATE (dimensions AS %s))`,
{{ end }}`,
templateContext(inputContext{
Start: input.Start,
End: input.End,
StartForInterval: startForInterval,
MainTableRequired: options.mainTableRequired,
Points: input.Points,
Units: units,
}),
withStr, axis, strings.Join(fields, ",\n "), where, offsetShift, offsetShift, withStr, axis, strings.Join(fields, ",\n "), where, offsetShift, offsetShift,
dimensionsInterpolate, dimensionsInterpolate,
) )
return strings.TrimSpace(sqlQuery)
context := inputContext{
Start: input.Start,
End: input.End,
StartForTableSelection: startForInterval,
MainTableRequired: options.mainTableRequired,
Points: input.Points,
Units: units,
}
return templateQuery{
Template: strings.TrimSpace(template),
Context: context,
}
} }
// toSQL converts a graph input to an SQL request // toSQL converts a graph input to an SQL request
func (input graphLineHandlerInput) toSQL() string { func (input graphLineHandlerInput) toSQL() []templateQuery {
// Calculate mainTableRequired once and use it for all axes to ensure // Calculate mainTableRequired once and use it for all axes to ensure
// consistency. This is useful as previous period will remove the // consistency. This is useful as previous period will remove the
// dimensions. // dimensions.
mainTableRequired := requireMainTable(input.schema, input.Dimensions, input.Filter) mainTableRequired := requireMainTable(input.schema, input.Dimensions, input.Filter)
parts := []string{input.toSQL1(1, toSQL1Options{ queries := []templateQuery{input.toSQL1(1, toSQL1Options{
mainTableRequired: mainTableRequired, mainTableRequired: mainTableRequired,
})} })}
if input.Bidirectional { if input.Bidirectional {
parts = append(parts, input.reverseDirection().toSQL1(2, toSQL1Options{ queries = append(queries, input.reverseDirection().toSQL1(2, toSQL1Options{
skipWithClause: true, skipWithClause: true,
reverseDirection: true, reverseDirection: true,
mainTableRequired: mainTableRequired, mainTableRequired: mainTableRequired,
})) }))
} }
if input.PreviousPeriod { if input.PreviousPeriod {
parts = append(parts, input.previousPeriod().toSQL1(3, toSQL1Options{ queries = append(queries, input.previousPeriod().toSQL1(3, toSQL1Options{
skipWithClause: true, skipWithClause: true,
offsetedStart: input.Start, offsetedStart: input.Start,
mainTableRequired: mainTableRequired, mainTableRequired: mainTableRequired,
})) }))
} }
if input.Bidirectional && input.PreviousPeriod { if input.Bidirectional && input.PreviousPeriod {
parts = append(parts, input.reverseDirection().previousPeriod().toSQL1(4, toSQL1Options{ queries = append(queries, input.reverseDirection().previousPeriod().toSQL1(4, toSQL1Options{
skipWithClause: true, skipWithClause: true,
reverseDirection: true, reverseDirection: true,
offsetedStart: input.Start, offsetedStart: input.Start,
mainTableRequired: mainTableRequired, mainTableRequired: mainTableRequired,
})) }))
} }
return strings.Join(parts, "\nUNION ALL\n") return queries
} }
func (c *Component) graphLineHandlerFunc(gc *gin.Context) { func (c *Component) graphLineHandlerFunc(gc *gin.Context) {
@@ -243,8 +246,8 @@ func (c *Component) graphLineHandlerFunc(gc *gin.Context) {
return return
} }
sqlQuery := input.toSQL() queries := input.toSQL()
sqlQuery = c.finalizeQuery(sqlQuery) sqlQuery := c.finalizeTemplateQueries(queries)
gc.Header("X-SQL-Query", strings.ReplaceAll(sqlQuery, "\n", " ")) gc.Header("X-SQL-Query", strings.ReplaceAll(sqlQuery, "\n", " "))
results := []struct { results := []struct {

View File

@@ -5,7 +5,6 @@ package console
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"time" "time"
@@ -165,7 +164,7 @@ func TestGraphQuerySQL(t *testing.T) {
Description string Description string
Pos helpers.Pos Pos helpers.Pos
Input graphLineHandlerInput Input graphLineHandlerInput
Expected string Expected []templateQuery
}{ }{
{ {
Description: "no dimensions, no filters, bps", Description: "no dimensions, no filters, bps",
@@ -180,9 +179,15 @@ func TestGraphQuerySQL(t *testing.T) {
}, },
Points: 100, Points: 100,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1) source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
SELECT SELECT
@@ -196,8 +201,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }}`, },
},
}, { }, {
Description: "no dimensions, no filters, l2 bps", Description: "no dimensions, no filters, l2 bps",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -211,9 +217,15 @@ ORDER BY time WITH FILL
}, },
Points: 100, Points: 100,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l2bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l2bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1) source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
SELECT SELECT
@@ -227,9 +239,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }} },
`, },
}, { }, {
Description: "no dimensions, no filters, pps", Description: "no dimensions, no filters, pps",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -243,9 +255,15 @@ ORDER BY time WITH FILL
}, },
Points: 100, Points: 100,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"pps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "pps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1) source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
SELECT SELECT
@@ -259,8 +277,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }}`, },
},
}, { }, {
Description: "truncated source address", Description: "truncated source address",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -276,9 +295,16 @@ ORDER BY time WITH FILL
}, },
Points: 100, Points: 100,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","main-table-required":true,"points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
MainTableRequired: true,
},
Template: `WITH
source AS (SELECT * REPLACE (tupleElement(IPv6CIDRToRange(SrcAddr, if(tupleElement(IPv6CIDRToRange(SrcAddr, 96), 1) = toIPv6('::ffff:0.0.0.0'), 120, 48)), 1) AS SrcAddr) FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * REPLACE (tupleElement(IPv6CIDRToRange(SrcAddr, if(tupleElement(IPv6CIDRToRange(SrcAddr, 96), 1) = toIPv6('::ffff:0.0.0.0'), 120, 48)), 1) AS SrcAddr) FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
rows AS (SELECT SrcAddr FROM source WHERE {{ .Timefilter }} AND (SrcAddr BETWEEN toIPv6('::ffff:1.0.0.0') AND toIPv6('::ffff:1.255.255.255')) GROUP BY SrcAddr ORDER BY {{ .Units }} DESC LIMIT 0) rows AS (SELECT SrcAddr FROM source WHERE {{ .Timefilter }} AND (SrcAddr BETWEEN toIPv6('::ffff:1.0.0.0') AND toIPv6('::ffff:1.255.255.255')) GROUP BY SrcAddr ORDER BY {{ .Units }} DESC LIMIT 0)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
@@ -293,8 +319,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS ['Other'])) INTERPOLATE (dimensions AS ['Other']))`,
{{ end }}`, },
},
}, { }, {
Description: "no dimensions", Description: "no dimensions",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -308,9 +335,15 @@ ORDER BY time WITH FILL
}, },
Points: 100, Points: 100,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1) source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
SELECT SELECT
@@ -324,8 +357,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }}`, },
},
}, { }, {
Description: "no dimensions, escaped filter", Description: "no dimensions, escaped filter",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -339,9 +373,15 @@ ORDER BY time WITH FILL
}, },
Points: 100, Points: 100,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1) source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
SELECT SELECT
@@ -355,8 +395,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }}`, },
},
}, { }, {
Description: "no dimensions, reverse direction", Description: "no dimensions, reverse direction",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -371,9 +412,15 @@ ORDER BY time WITH FILL
Points: 100, Points: 100,
Bidirectional: true, Bidirectional: true,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1) source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
SELECT SELECT
@@ -387,11 +434,15 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }} }, {
UNION ALL Context: inputContext{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
SELECT 2 AS axis, * FROM ( End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `SELECT 2 AS axis, * FROM (
SELECT SELECT
{{ call .ToStartOfInterval "TimeReceived" }} AS time, {{ call .ToStartOfInterval "TimeReceived" }} AS time,
{{ .Units }}/{{ .Interval }} AS xps, {{ .Units }}/{{ .Interval }} AS xps,
@@ -403,8 +454,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }}`, },
},
}, { }, {
Description: "no dimensions, reverse direction, inl2%", Description: "no dimensions, reverse direction, inl2%",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -419,9 +471,15 @@ ORDER BY time WITH FILL
Points: 100, Points: 100,
Bidirectional: true, Bidirectional: true,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"inl2%"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "inl2%",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1) source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
SELECT SELECT
@@ -435,11 +493,15 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }} }, {
UNION ALL Context: inputContext{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"outl2%"}@@ }} Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
SELECT 2 AS axis, * FROM ( End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "outl2%",
},
Template: `SELECT 2 AS axis, * FROM (
SELECT SELECT
{{ call .ToStartOfInterval "TimeReceived" }} AS time, {{ call .ToStartOfInterval "TimeReceived" }} AS time,
{{ .Units }}/{{ .Interval }} AS xps, {{ .Units }}/{{ .Interval }} AS xps,
@@ -451,8 +513,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }}`, },
},
}, { }, {
Description: "no filters", Description: "no filters",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -470,9 +533,15 @@ ORDER BY time WITH FILL
}, },
Points: 100, Points: 100,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
rows AS (SELECT ExporterName, InIfProvider FROM source WHERE {{ .Timefilter }} GROUP BY ExporterName, InIfProvider ORDER BY {{ .Units }} DESC LIMIT 20) rows AS (SELECT ExporterName, InIfProvider FROM source WHERE {{ .Timefilter }} GROUP BY ExporterName, InIfProvider ORDER BY {{ .Units }} DESC LIMIT 20)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
@@ -487,8 +556,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS ['Other', 'Other'])) INTERPOLATE (dimensions AS ['Other', 'Other']))`,
{{ end }}`, },
},
}, { }, {
Description: "no filters, limitType by max", Description: "no filters, limitType by max",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -507,9 +577,15 @@ ORDER BY time WITH FILL
}, },
Points: 100, Points: 100,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
rows AS (SELECT ExporterName, InIfProvider FROM ( SELECT ExporterName, InIfProvider, {{ .Units }} AS sum_at_time FROM source WHERE {{ .Timefilter }} GROUP BY ExporterName, InIfProvider ) GROUP BY ExporterName, InIfProvider ORDER BY MAX(sum_at_time) DESC LIMIT 20) rows AS (SELECT ExporterName, InIfProvider FROM ( SELECT ExporterName, InIfProvider, {{ .Units }} AS sum_at_time FROM source WHERE {{ .Timefilter }} GROUP BY ExporterName, InIfProvider ) GROUP BY ExporterName, InIfProvider ORDER BY MAX(sum_at_time) DESC LIMIT 20)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
@@ -524,8 +600,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS ['Other', 'Other'])) INTERPOLATE (dimensions AS ['Other', 'Other']))`,
{{ end }}`, },
},
}, { }, {
Description: "no filters, reverse", Description: "no filters, reverse",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -544,9 +621,15 @@ ORDER BY time WITH FILL
Points: 100, Points: 100,
Bidirectional: true, Bidirectional: true,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
rows AS (SELECT ExporterName, InIfProvider FROM source WHERE {{ .Timefilter }} GROUP BY ExporterName, InIfProvider ORDER BY {{ .Units }} DESC LIMIT 20) rows AS (SELECT ExporterName, InIfProvider FROM source WHERE {{ .Timefilter }} GROUP BY ExporterName, InIfProvider ORDER BY {{ .Units }} DESC LIMIT 20)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
@@ -561,11 +644,15 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS ['Other', 'Other'])) INTERPOLATE (dimensions AS ['Other', 'Other']))`,
{{ end }} }, {
UNION ALL Context: inputContext{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
SELECT 2 AS axis, * FROM ( End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `SELECT 2 AS axis, * FROM (
SELECT SELECT
{{ call .ToStartOfInterval "TimeReceived" }} AS time, {{ call .ToStartOfInterval "TimeReceived" }} AS time,
{{ .Units }}/{{ .Interval }} AS xps, {{ .Units }}/{{ .Interval }} AS xps,
@@ -577,8 +664,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS ['Other', 'Other'])) INTERPOLATE (dimensions AS ['Other', 'Other']))`,
{{ end }}`, },
},
}, { }, {
Description: "no filters, previous period", Description: "no filters, previous period",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -597,9 +685,15 @@ ORDER BY time WITH FILL
Points: 100, Points: 100,
PreviousPeriod: true, PreviousPeriod: true,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
rows AS (SELECT ExporterName, InIfProvider FROM source WHERE {{ .Timefilter }} GROUP BY ExporterName, InIfProvider ORDER BY {{ .Units }} DESC LIMIT 20) rows AS (SELECT ExporterName, InIfProvider FROM source WHERE {{ .Timefilter }} GROUP BY ExporterName, InIfProvider ORDER BY {{ .Units }} DESC LIMIT 20)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
@@ -614,11 +708,19 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS ['Other', 'Other'])) INTERPOLATE (dimensions AS ['Other', 'Other']))`,
{{ end }} }, {
UNION ALL Context: inputContext{
{{ with context @@{"start":"2022-04-09T15:45:10Z","end":"2022-04-10T15:45:10Z","start-for-interval":"2022-04-10T15:45:10Z","points":100,"units":"l3bps"}@@ }} Start: time.Date(2022, 4, 9, 15, 45, 10, 0, time.UTC),
SELECT 3 AS axis, * FROM ( End: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
StartForTableSelection: func() *time.Time {
t := time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC)
return &t
}(),
Points: 100,
Units: "l3bps",
},
Template: `SELECT 3 AS axis, * FROM (
SELECT SELECT
{{ call .ToStartOfInterval "TimeReceived" }} + INTERVAL 86400 second AS time, {{ call .ToStartOfInterval "TimeReceived" }} + INTERVAL 86400 second AS time,
{{ .Units }}/{{ .Interval }} AS xps, {{ .Units }}/{{ .Interval }} AS xps,
@@ -630,8 +732,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} + INTERVAL 86400 second FROM {{ .TimefilterStart }} + INTERVAL 86400 second
TO {{ .TimefilterEnd }} + INTERVAL 1 second + INTERVAL 86400 second TO {{ .TimefilterEnd }} + INTERVAL 1 second + INTERVAL 86400 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }}`, },
},
}, { }, {
Description: "previous period while main table is required", Description: "previous period while main table is required",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -649,9 +752,16 @@ ORDER BY time WITH FILL
Points: 100, Points: 100,
PreviousPeriod: true, PreviousPeriod: true,
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","main-table-required":true,"points":100,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
MainTableRequired: true,
Points: 100,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
rows AS (SELECT SrcAddr, DstAddr FROM source WHERE {{ .Timefilter }} AND (InIfBoundary = 'external') GROUP BY SrcAddr, DstAddr ORDER BY {{ .Units }} DESC LIMIT 0) rows AS (SELECT SrcAddr, DstAddr FROM source WHERE {{ .Timefilter }} AND (InIfBoundary = 'external') GROUP BY SrcAddr, DstAddr ORDER BY {{ .Units }} DESC LIMIT 0)
SELECT 1 AS axis, * FROM ( SELECT 1 AS axis, * FROM (
@@ -666,11 +776,20 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS ['Other', 'Other'])) INTERPOLATE (dimensions AS ['Other', 'Other']))`,
{{ end }} }, {
UNION ALL Context: inputContext{
{{ with context @@{"start":"2022-04-09T15:45:10Z","end":"2022-04-10T15:45:10Z","start-for-interval":"2022-04-10T15:45:10Z","main-table-required":true,"points":100,"units":"l3bps"}@@ }} Start: time.Date(2022, 4, 9, 15, 45, 10, 0, time.UTC),
SELECT 3 AS axis, * FROM ( End: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
StartForTableSelection: func() *time.Time {
t := time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC)
return &t
}(),
MainTableRequired: true,
Points: 100,
Units: "l3bps",
},
Template: `SELECT 3 AS axis, * FROM (
SELECT SELECT
{{ call .ToStartOfInterval "TimeReceived" }} + INTERVAL 86400 second AS time, {{ call .ToStartOfInterval "TimeReceived" }} + INTERVAL 86400 second AS time,
{{ .Units }}/{{ .Interval }} AS xps, {{ .Units }}/{{ .Interval }} AS xps,
@@ -682,8 +801,9 @@ ORDER BY time WITH FILL
FROM {{ .TimefilterStart }} + INTERVAL 86400 second FROM {{ .TimefilterStart }} + INTERVAL 86400 second
TO {{ .TimefilterEnd }} + INTERVAL 1 second + INTERVAL 86400 second TO {{ .TimefilterEnd }} + INTERVAL 1 second + INTERVAL 86400 second
STEP {{ .Interval }} STEP {{ .Interval }}
INTERPOLATE (dimensions AS emptyArrayString())) INTERPOLATE (dimensions AS emptyArrayString()))`,
{{ end }}`, },
},
}, },
} }
for _, tc := range cases { for _, tc := range cases {
@@ -694,11 +814,9 @@ ORDER BY time WITH FILL
if err := tc.Input.Filter.Validate(tc.Input.schema); err != nil { if err := tc.Input.Filter.Validate(tc.Input.schema); err != nil {
t.Fatalf("%sValidate() error:\n%+v", tc.Pos, err) t.Fatalf("%sValidate() error:\n%+v", tc.Pos, err)
} }
tc.Expected = strings.ReplaceAll(tc.Expected, "@@", "`")
t.Run(tc.Description, func(t *testing.T) { t.Run(tc.Description, func(t *testing.T) {
got := tc.Input.toSQL() got := tc.Input.toSQL()
if diff := helpers.Diff(strings.Split(strings.TrimSpace(got), "\n"), if diff := helpers.Diff(got, tc.Expected); diff != "" {
strings.Split(strings.TrimSpace(tc.Expected), "\n")); diff != "" {
t.Errorf("%stoSQL (-got, +want):\n%s", tc.Pos, diff) t.Errorf("%stoSQL (-got, +want):\n%s", tc.Pos, diff)
} }
}) })

View File

@@ -36,7 +36,7 @@ type sankeyLink struct {
} }
// sankeyHandlerInputToSQL converts a sankey query to an SQL request // sankeyHandlerInputToSQL converts a sankey query to an SQL request
func (input graphSankeyHandlerInput) toSQL() (string, error) { func (input graphSankeyHandlerInput) toSQL() ([]templateQuery, error) {
where := templateWhere(input.Filter) where := templateWhere(input.Filter)
// Select // Select
@@ -61,8 +61,7 @@ func (input graphSankeyHandlerInput) toSQL() (string, error) {
} }
with = append(with, selectSankeyRowsByLimitType(input, dimensions, where)) with = append(with, selectSankeyRowsByLimitType(input, dimensions, where))
sqlQuery := fmt.Sprintf(` template := fmt.Sprintf(`
{{ with %s }}
WITH WITH
%s %s
SELECT SELECT
@@ -70,17 +69,21 @@ SELECT
FROM source FROM source
WHERE %s WHERE %s
GROUP BY dimensions GROUP BY dimensions
ORDER BY xps DESC ORDER BY xps DESC`,
{{ end }}`, strings.Join(with, ",\n "), strings.Join(fields, ",\n "), where)
templateContext(inputContext{
context := inputContext{
Start: input.Start, Start: input.Start,
End: input.End, End: input.End,
MainTableRequired: requireMainTable(input.schema, input.Dimensions, input.Filter), MainTableRequired: requireMainTable(input.schema, input.Dimensions, input.Filter),
Points: 20, Points: 20,
Units: input.Units, Units: input.Units,
}), }
strings.Join(with, ",\n "), strings.Join(fields, ",\n "), where)
return strings.TrimSpace(sqlQuery), nil return []templateQuery{{
Template: strings.TrimSpace(template),
Context: context,
}}, nil
} }
func (c *Component) graphSankeyHandlerFunc(gc *gin.Context) { func (c *Component) graphSankeyHandlerFunc(gc *gin.Context) {
@@ -105,14 +108,14 @@ func (c *Component) graphSankeyHandlerFunc(gc *gin.Context) {
return return
} }
sqlQuery, err := input.toSQL() queries, err := input.toSQL()
if err != nil { if err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())}) gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return return
} }
// Prepare and execute query // Prepare and execute query
sqlQuery = c.finalizeQuery(sqlQuery) sqlQuery := c.finalizeTemplateQueries(queries)
gc.Header("X-SQL-Query", strings.ReplaceAll(sqlQuery, "\n", " ")) gc.Header("X-SQL-Query", strings.ReplaceAll(sqlQuery, "\n", " "))
results := []struct { results := []struct {
Xps float64 `ch:"xps"` Xps float64 `ch:"xps"`

View File

@@ -4,7 +4,6 @@
package console package console
import ( import (
"strings"
"testing" "testing"
"time" "time"
@@ -21,7 +20,7 @@ func TestSankeyQuerySQL(t *testing.T) {
Description string Description string
Pos helpers.Pos Pos helpers.Pos
Input graphSankeyHandlerInput Input graphSankeyHandlerInput
Expected string Expected []templateQuery
}{ }{
{ {
Description: "two dimensions, no filters, l3 bps", Description: "two dimensions, no filters, l3 bps",
@@ -39,9 +38,15 @@ func TestSankeyQuerySQL(t *testing.T) {
Units: "l3bps", Units: "l3bps",
}, },
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 20,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
(SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range, (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range,
rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5) rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5)
@@ -52,8 +57,9 @@ SELECT
FROM source FROM source
WHERE {{ .Timefilter }} WHERE {{ .Timefilter }}
GROUP BY dimensions GROUP BY dimensions
ORDER BY xps DESC ORDER BY xps DESC`,
{{ end }}`, },
},
}, { }, {
Description: "two dimensions, no filters, l3 bps, limitType by max", Description: "two dimensions, no filters, l3 bps, limitType by max",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -71,9 +77,15 @@ ORDER BY xps DESC
Units: "l3bps", Units: "l3bps",
}, },
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 20,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
(SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range, (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range,
rows AS (SELECT SrcAS, ExporterName FROM ( SELECT SrcAS, ExporterName, {{ .Units }} AS sum_at_time FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ) GROUP BY SrcAS, ExporterName ORDER BY MAX(sum_at_time) DESC LIMIT 5) rows AS (SELECT SrcAS, ExporterName FROM ( SELECT SrcAS, ExporterName, {{ .Units }} AS sum_at_time FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ) GROUP BY SrcAS, ExporterName ORDER BY MAX(sum_at_time) DESC LIMIT 5)
@@ -84,8 +96,9 @@ SELECT
FROM source FROM source
WHERE {{ .Timefilter }} WHERE {{ .Timefilter }}
GROUP BY dimensions GROUP BY dimensions
ORDER BY xps DESC ORDER BY xps DESC`,
{{ end }}`, },
},
}, { }, {
Description: "two dimensions, no filters, l2 bps", Description: "two dimensions, no filters, l2 bps",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -102,9 +115,15 @@ ORDER BY xps DESC
Units: "l2bps", Units: "l2bps",
}, },
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l2bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 20,
Units: "l2bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
(SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range, (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range,
rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5) rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5)
@@ -115,9 +134,9 @@ SELECT
FROM source FROM source
WHERE {{ .Timefilter }} WHERE {{ .Timefilter }}
GROUP BY dimensions GROUP BY dimensions
ORDER BY xps DESC ORDER BY xps DESC`,
{{ end }} },
`, },
}, { }, {
Description: "two dimensions, no filters, pps", Description: "two dimensions, no filters, pps",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -134,9 +153,15 @@ ORDER BY xps DESC
Units: "pps", Units: "pps",
}, },
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"pps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 20,
Units: "pps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
(SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range, (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }}) AS range,
rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5) rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 5)
@@ -147,8 +172,9 @@ SELECT
FROM source FROM source
WHERE {{ .Timefilter }} WHERE {{ .Timefilter }}
GROUP BY dimensions GROUP BY dimensions
ORDER BY xps DESC ORDER BY xps DESC`,
{{ end }}`, },
},
}, { }, {
Description: "two dimensions, with filter", Description: "two dimensions, with filter",
Pos: helpers.Mark(), Pos: helpers.Mark(),
@@ -165,9 +191,15 @@ ORDER BY xps DESC
Units: "l3bps", Units: "l3bps",
}, },
}, },
Expected: ` Expected: []templateQuery{
{{ with context @@{"start":"2022-04-10T15:45:10Z","end":"2022-04-11T15:45:10Z","points":20,"units":"l3bps"}@@ }} {
WITH Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 20,
Units: "l3bps",
},
Template: `WITH
source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1), source AS (SELECT * FROM {{ .Table }} SETTINGS asterisk_include_alias_columns = 1),
(SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }} AND (DstCountry = 'FR')) AS range, (SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM source WHERE {{ .Timefilter }} AND (DstCountry = 'FR')) AS range,
rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} AND (DstCountry = 'FR') GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 10) rows AS (SELECT SrcAS, ExporterName FROM source WHERE {{ .Timefilter }} AND (DstCountry = 'FR') GROUP BY SrcAS, ExporterName ORDER BY {{ .Units }} DESC LIMIT 10)
@@ -178,8 +210,9 @@ SELECT
FROM source FROM source
WHERE {{ .Timefilter }} AND (DstCountry = 'FR') WHERE {{ .Timefilter }} AND (DstCountry = 'FR')
GROUP BY dimensions GROUP BY dimensions
ORDER BY xps DESC ORDER BY xps DESC`,
{{ end }}`, },
},
}, },
} }
for _, tc := range cases { for _, tc := range cases {
@@ -190,11 +223,9 @@ ORDER BY xps DESC
if err := tc.Input.Filter.Validate(tc.Input.schema); err != nil { if err := tc.Input.Filter.Validate(tc.Input.schema); err != nil {
t.Fatalf("%sValidate() error:\n%+v", tc.Pos, err) t.Fatalf("%sValidate() error:\n%+v", tc.Pos, err)
} }
tc.Expected = strings.ReplaceAll(tc.Expected, "@@", "`")
t.Run(tc.Description, func(t *testing.T) { t.Run(tc.Description, func(t *testing.T) {
got, _ := tc.Input.toSQL() got, _ := tc.Input.toSQL()
if diff := helpers.Diff(strings.Split(strings.TrimSpace(got), "\n"), if diff := helpers.Diff(got, tc.Expected); diff != "" {
strings.Split(strings.TrimSpace(tc.Expected), "\n")); diff != "" {
t.Errorf("%stoSQL (-got, +want):\n%s", tc.Pos, diff) t.Errorf("%stoSQL (-got, +want):\n%s", tc.Pos, diff)
} }
}) })

View File

@@ -192,8 +192,7 @@ func (c *Component) widgetTopHandlerFunc(gc *gin.Context) {
} }
now := c.d.Clock.Now() now := c.d.Clock.Now()
query := c.finalizeQuery(fmt.Sprintf(` template := fmt.Sprintf(`
{{ with %s }}
WITH WITH
(SELECT SUM(Bytes*SamplingRate) FROM {{ .Table }} WHERE {{ .Timefilter }} %s) AS Total (SELECT SUM(Bytes*SamplingRate) FROM {{ .Table }} WHERE {{ .Timefilter }} %s) AS Total
SELECT SELECT
@@ -204,15 +203,18 @@ WHERE {{ .Timefilter }}
%s %s
GROUP BY %s GROUP BY %s
ORDER BY Percent DESC ORDER BY Percent DESC
LIMIT 5 LIMIT 5`,
{{ end }}`, filter, selector, selector, filter, groupby)
templateContext(inputContext{
query := c.finalizeTemplateQuery(templateQuery{
Template: template,
Context: inputContext{
Start: now.Add(-5 * time.Minute), Start: now.Add(-5 * time.Minute),
End: now, End: now,
MainTableRequired: mainTableRequired, MainTableRequired: mainTableRequired,
Points: 5, Points: 5,
}), },
filter, selector, selector, filter, groupby)) })
gc.Header("X-SQL-Query", query) gc.Header("X-SQL-Query", query)
results := []topResult{} results := []topResult{}
@@ -232,8 +234,7 @@ func (c *Component) widgetGraphHandlerFunc(gc *gin.Context) {
} }
ctx := c.t.Context(gc.Request.Context()) ctx := c.t.Context(gc.Request.Context())
now := c.d.Clock.Now() now := c.d.Clock.Now()
query := c.finalizeQuery(fmt.Sprintf(` template := fmt.Sprintf(`
{{ with %s }}
SELECT SELECT
{{ call .ToStartOfInterval "TimeReceived" }} AS Time, {{ call .ToStartOfInterval "TimeReceived" }} AS Time,
SUM(Bytes*SamplingRate*8/{{ .Interval }})/1000/1000/1000 AS Gbps SUM(Bytes*SamplingRate*8/{{ .Interval }})/1000/1000/1000 AS Gbps
@@ -244,15 +245,18 @@ GROUP BY Time
ORDER BY Time WITH FILL ORDER BY Time WITH FILL
FROM {{ .TimefilterStart }} FROM {{ .TimefilterStart }}
TO {{ .TimefilterEnd }} + INTERVAL 1 second TO {{ .TimefilterEnd }} + INTERVAL 1 second
STEP {{ .Interval }} STEP {{ .Interval }}`,
{{ end }}`, filter)
templateContext(inputContext{
query := c.finalizeTemplateQuery(templateQuery{
Template: template,
Context: inputContext{
Start: now.Add(-c.config.HomepageGraphTimeRange), Start: now.Add(-c.config.HomepageGraphTimeRange),
End: now, End: now,
MainTableRequired: false, MainTableRequired: false,
Points: 200, Points: 200,
}), },
filter)) })
gc.Header("X-SQL-Query", query) gc.Header("X-SQL-Query", query)
results := []struct { results := []struct {