From d700d76c39b69803c2aa94483bd696032bb90ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Mon, 20 Dec 2021 14:06:05 +0100 Subject: [PATCH] Allow format QL nodes to properly format their arguents --- pkg/ql/ast_nodes.go | 11 +++++- pkg/ql/squirrel.go | 13 ++++++- store/postgres/sql_functions.go | 2 +- store/tests/compose_records_test.go | 57 ++++++++++++++++++----------- 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/pkg/ql/ast_nodes.go b/pkg/ql/ast_nodes.go index 758389fd9..2a02ba3f9 100644 --- a/pkg/ql/ast_nodes.go +++ b/pkg/ql/ast_nodes.go @@ -2,8 +2,9 @@ package ql import ( "fmt" - "github.com/Masterminds/squirrel" "strings" + + "github.com/Masterminds/squirrel" ) // SelectStatement represents a SQL SELECT statement. @@ -15,6 +16,8 @@ type ( Validate() error } + replacer func(string) string + ASTSet []ASTNode // Stream of comma delimited nodes ASTNodes []ASTNode // Stream of space delimited nodes @@ -63,6 +66,7 @@ type ( NodeF struct { Expr string Arguments []ASTNode + replacer replacer } ) @@ -219,6 +223,11 @@ func (nn Columns) Strings() (out []string) { return } +// MakeReplacedFormattedNode also accepts the replacer to apply to the arguments +func MakeReplacedFormattedNode(expr string, r replacer, nn ...ASTNode) *NodeF { + return &NodeF{Expr: expr, Arguments: nn, replacer: r} +} + func MakeFormattedNode(expr string, nn ...ASTNode) *NodeF { return &NodeF{Expr: expr, Arguments: nn} } diff --git a/pkg/ql/squirrel.go b/pkg/ql/squirrel.go index 8a09fdf5d..df852e229 100644 --- a/pkg/ql/squirrel.go +++ b/pkg/ql/squirrel.go @@ -127,7 +127,18 @@ func (n NodeF) ToSql() (string, []interface{}, error) { adtArgs []interface{} ) - for _, s := range n.Arguments { + for i, s := range n.Arguments { + // When provided, apply the replacer over the arguments of the node. + // We can skip any node other then LString as thats the only one we can apply it to (currently) + if n.replacer != nil { + if c, ok := s.(LString); ok { + c.Value = n.replacer(c.Value) + // Updating the originals as we're dealing with values + n.Arguments[i] = c + s = c + } + } + if fa, aa, err := s.ToSql(); err != nil { return "", nil, err } else { diff --git a/store/postgres/sql_functions.go b/store/postgres/sql_functions.go index e2ebfe7fb..122794a3a 100644 --- a/store/postgres/sql_functions.go +++ b/store/postgres/sql_functions.go @@ -42,7 +42,7 @@ func sqlFunctionHandler(f ql.Function) (ql.ASTNode, error) { case "QUARTER", "YEAR": return ql.MakeFormattedNode(fmt.Sprintf("EXTRACT(%s FROM %%s::date)", f.Name), f.Arguments...), nil case "DATE_FORMAT": - return ql.MakeFormattedNode("TO_CHAR(%s, %s)", f.Arguments...), nil + return ql.MakeReplacedFormattedNode("TO_CHAR(%s, %s)", translateDateFormatParams, f.Arguments...), nil case "DATE": return ql.MakeFormattedNode("%s::DATE", f.Arguments...), nil case "DATE_ADD", "DATE_SUB", "STD": diff --git a/store/tests/compose_records_test.go b/store/tests/compose_records_test.go index b693b2f87..78bcd6e31 100644 --- a/store/tests/compose_records_test.go +++ b/store/tests/compose_records_test.go @@ -1567,35 +1567,48 @@ func testComposeRecords(t *testing.T, s store.ComposeRecords) { report []map[string]interface{} ) - report, err = s.ComposeRecordReport(ctx, mod, "MAX(num1)", "QUARTER(dt1)", "") - req.NoError(err) - req.Len(report, 3) + t.Run("base", func(t *testing.T) { + report, err = s.ComposeRecordReport(ctx, mod, "MAX(num1)", "QUARTER(dt1)", "") + req.NoError(err) + req.Len(report, 3) - // @todo find a way to compare the results + // @todo find a way to compare the results - //expected := []map[string]interface{}{ - // {"count": 3, "dimension_0": 1, "metric_0": 3}, - // {"count": 2, "dimension_0": 2, "metric_0": 5}, - // {"count": 1, "dimension_0": nil, "metric_0": nil}, - //} - // - //req.True( - // reflect.DeepEqual(report, expected), - // "report does not match expected results:\n%#v\n%#v", report, expected) + //expected := []map[string]interface{}{ + // {"count": 3, "dimension_0": 1, "metric_0": 3}, + // {"count": 2, "dimension_0": 2, "metric_0": 5}, + // {"count": 1, "dimension_0": nil, "metric_0": nil}, + //} + // + //req.True( + // reflect.DeepEqual(report, expected), + // "report does not match expected results:\n%#v\n%#v", report, expected) - report, err = s.ComposeRecordReport(ctx, mod, "COUNT(num1)", "YEAR(dt1)", "") - req.NoError(err) + report, err = s.ComposeRecordReport(ctx, mod, "COUNT(num1)", "YEAR(dt1)", "") + req.NoError(err) - report, err = s.ComposeRecordReport(ctx, mod, "SUM(num1)", "DATE(dt1)", "") - req.NoError(err) + report, err = s.ComposeRecordReport(ctx, mod, "SUM(num1)", "DATE(dt1)", "") + req.NoError(err) - report, err = s.ComposeRecordReport(ctx, mod, "MIN(num1)", "DATE(NOW())", "") - req.NoError(err) + report, err = s.ComposeRecordReport(ctx, mod, "MIN(num1)", "DATE(NOW())", "") + req.NoError(err) - report, err = s.ComposeRecordReport(ctx, mod, "AVG(num1)", "DATE(NOW())", "") - req.NoError(err) + report, err = s.ComposeRecordReport(ctx, mod, "AVG(num1)", "DATE(NOW())", "") + req.NoError(err) + + // Note that not all functions are compatible across all backends + }) + + t.Run("date formatting", func(t *testing.T) { + report, err = s.ComposeRecordReport(ctx, mod, "MAX(num1)", "DATE_FORMAT(dt1, '%Y-%m-01')", "") + req.NoError(err) + req.Len(report, 3) + + report, err = s.ComposeRecordReport(ctx, mod, "MAX(num1)", "DATE_FORMAT(dt1, '%y; %j @ %H %i %p')", "") + req.NoError(err) + req.Len(report, 3) + }) - // Note that not all functions are compatible across all backends }) t.Run("partial value update", func(t *testing.T) {