From d8250cea0f638e51259f05a5e34ae4429d0945f0 Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Sat, 24 Oct 2026 05:33:27 +0800 Subject: [PATCH 1/2] fix: preserve commas in SQL IN lists Keep multi-value IN predicates intact when the SQL AST is formatted during ad-hoc filter application. Fixes #941. --- pkg/eval/eval_query.go | 6 ++-- pkg/eval/eval_query_regression_test.go | 38 ++++++++++++++++++++++++++ pkg/resource_handlers_test.go | 9 ++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 pkg/eval/eval_query_regression_test.go diff --git a/pkg/eval/eval_query.go b/pkg/eval/eval_query.go index da0a9416c..fe9be4ca0 100644 --- a/pkg/eval/eval_query.go +++ b/pkg/eval/eval_query.go @@ -1726,11 +1726,11 @@ func (s *EvalQueryScanner) ToAST() (*EvalAST, error) { } if subAST.HasOwnProperty("root") && len(subAST.Obj["root"].(*EvalAST).Arr) > 0 { var subArr = subAST.Obj["root"].(*EvalAST) - argument += " (" + items := make([]string, 0, len(subArr.Arr)) for _, item := range subArr.Arr { - argument += item.(string) + items = append(items, item.(string)) } - argument = argument + ")" + argument += " (" + strings.Join(items, ", ") + ")" } else { argument += " (" + newLine + PrintAST(subAST, tabSize) + ")" if s.RootToken != "select" { diff --git a/pkg/eval/eval_query_regression_test.go b/pkg/eval/eval_query_regression_test.go new file mode 100644 index 000000000..f8cc687b2 --- /dev/null +++ b/pkg/eval/eval_query_regression_test.go @@ -0,0 +1,38 @@ +package eval + +import ( + "strings" + "testing" +) + +func TestScannerPreservesInListSeparators(t *testing.T) { + tests := []struct { + name string + query string + want string + }{ + { + name: "string values", + query: "SELECT dt FROM default.messages WHERE severity IN ('EMERGENCY','ALERT','CRITICAL')", + want: "severity IN ('EMERGENCY', 'ALERT', 'CRITICAL')", + }, + { + name: "numeric values", + query: "SELECT id FROM default.messages WHERE id IN (1,2,3)", + want: "id IN (1, 2, 3)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + scanner := NewScanner(tt.query) + formatted, err := scanner.Format() + if err != nil { + t.Fatalf("format query: %v", err) + } + if !strings.Contains(formatted, tt.want) { + t.Fatalf("formatted query %q does not contain %q", formatted, tt.want) + } + }) + } +} diff --git a/pkg/resource_handlers_test.go b/pkg/resource_handlers_test.go index 5486749ae..e8f041100 100644 --- a/pkg/resource_handlers_test.go +++ b/pkg/resource_handlers_test.go @@ -207,6 +207,15 @@ func TestHandleApplyAdhocFilters(t *testing.T) { map[string]interface{}{"query": "SELECT x FROM default.test", "adhocFilters": filters, "target": target}, http.StatusOK, []string{"WHERE", "b = 2"}, "", }, + { + "preserve IN list when injecting filters", + map[string]interface{}{ + "query": "SELECT dt FROM default.messages WHERE severity IN ('EMERGENCY','ALERT','CRITICAL')", + "adhocFilters": []map[string]interface{}{{"key": "environment", "operator": "=", "value": "test"}}, + "target": target, + }, + http.StatusOK, []string{"severity IN ('EMERGENCY', 'ALERT', 'CRITICAL')", "(environment = 'test')"}, "", + }, { "string value quoted", map[string]interface{}{"query": "SELECT x FROM default.test", "adhocFilters": []map[string]interface{}{{"key": "b", "operator": "=", "value": "web"}}, "target": target}, From b4cf649edb96750712ce623d057883611c50aa6f Mon Sep 17 00:00:00 2001 From: Roman M Date: Sun, 1 Nov 2026 23:20:45 +0100 Subject: [PATCH 2/2] test: e2e for IN list with ad-hoc filter (#941) --- .../dashboards/adhoc_in_list_issue_941.json | 35 +++++++++++++++++++ .../testflows/tests/automated/adhoc_macro.py | 10 ++++++ 2 files changed, 45 insertions(+) create mode 100644 docker/grafana/dashboards/adhoc_in_list_issue_941.json diff --git a/docker/grafana/dashboards/adhoc_in_list_issue_941.json b/docker/grafana/dashboards/adhoc_in_list_issue_941.json new file mode 100644 index 000000000..3a0034cb7 --- /dev/null +++ b/docker/grafana/dashboards/adhoc_in_list_issue_941.json @@ -0,0 +1,35 @@ +{ + "uid": "issue-941-in-list", + "title": "issue 941 adhoc with IN list", + "editable": true, + "schemaVersion": 39, + "time": {"from": "now-6h", "to": "now"}, + "templating": { + "list": [ + { + "type": "adhoc", + "name": "adhoc", + "datasource": {"type": "vertamedia-clickhouse-datasource", "uid": "P7E099F39B84EA795"}, + "filters": [{"key": "default.test_grafana.country", "operator": "=", "value": "US", "condition": ""}] + } + ] + }, + "panels": [ + { + "id": 1, + "type": "table", + "title": "IN list with adhoc filter", + "datasource": {"type": "vertamedia-clickhouse-datasource", "uid": "P7E099F39B84EA795"}, + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}, + "targets": [ + { + "refId": "A", + "datasource": {"type": "vertamedia-clickhouse-datasource", "uid": "P7E099F39B84EA795"}, + "format": "table", + "editorMode": "sql", + "query": "SELECT service_name, count() AS c FROM default.test_grafana WHERE service_name IN ('mysql','postgresql') GROUP BY service_name ORDER BY service_name" + } + ] + } + ] +} diff --git a/tests/testflows/tests/automated/adhoc_macro.py b/tests/testflows/tests/automated/adhoc_macro.py index e275aa9c5..d54999c5d 100644 --- a/tests/testflows/tests/automated/adhoc_macro.py +++ b/tests/testflows/tests/automated/adhoc_macro.py @@ -171,6 +171,16 @@ def adhoc_from_template_variable_issue_805(self): ) +@TestScenario +def adhoc_in_list_issue_941(self): + adhoc_dashboard_check_outline( + dashboard_name="issue 941 adhoc with IN list", + panels=[ + ("IN list with adhoc filter", ["service_name IN ('mysql', 'postgresql')", "country = 'US'"]), + ], + ) + + @TestScenario def adhoc_hide_table_names_issue_456(self): """Check that adhoc dashboard 'AdHoc with Hiding table name' shows correct SQL and data."""