From f4b5bc7629c4a60ec3afdbbe327924f4b57ded89 Mon Sep 17 00:00:00 2001 From: wangyelei Date: Mon, 15 Jun 2026 15:33:10 +0800 Subject: [PATCH] fix: enrichQueryWithOnCluster misses ON CLUSTER for views with UUID/column-defs The existing regex patterns used [^(]+ in the first capture group, which stops at '(' from column definitions or UUID, preventing AS SELECT/AS WITH matching. Changed createViewRe and attachViewRe to use .+? so that UUID and parenthesized column definitions between the view name and the AS clause are skipped during match, allowing ON CLUSTER injection to succeed. Fixes ATTACH LIVE VIEW, ATTACH WINDOW VIEW, and any other view types whose SHOW CREATE output includes UUID and/or explicit column definitions. --- pkg/clickhouse/clickhouse.go | 13 +++++++------ pkg/clickhouse/clickhouse_test.go | 7 +++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pkg/clickhouse/clickhouse.go b/pkg/clickhouse/clickhouse.go index e615a0812..37b5a397c 100644 --- a/pkg/clickhouse/clickhouse.go +++ b/pkg/clickhouse/clickhouse.go @@ -14,10 +14,6 @@ import ( "sync" "time" - "github.com/Altinity/clickhouse-backup/v2/pkg/common" - "github.com/Altinity/clickhouse-backup/v2/pkg/config" - "github.com/Altinity/clickhouse-backup/v2/pkg/metadata" - "github.com/Altinity/clickhouse-backup/v2/pkg/utils" "github.com/ClickHouse/clickhouse-go/v2" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "github.com/antchfx/xmlquery" @@ -26,6 +22,11 @@ import ( "github.com/ricochet2200/go-disk-usage/du" "github.com/rs/zerolog" "github.com/rs/zerolog/log" + + "github.com/Altinity/clickhouse-backup/v2/pkg/common" + "github.com/Altinity/clickhouse-backup/v2/pkg/config" + "github.com/Altinity/clickhouse-backup/v2/pkg/metadata" + "github.com/Altinity/clickhouse-backup/v2/pkg/utils" ) // ClickHouse - provide @@ -1039,11 +1040,11 @@ func (ch *ClickHouse) DropOrDetachTable(table Table, query, onCluster string, ig var createViewRefreshRe = regexp.MustCompile(`(?im)^(CREATE[\s\w]+VIEW[^(]+)(\s+REFRESH\s+.+)`) var createViewToClauseRe = regexp.MustCompile(`(?im)^(CREATE[\s\w]+VIEW[^(]+)(\s+TO\s+.+)`) var createViewAsWithRe = regexp.MustCompile(`(?im)^(CREATE[\s\w]+VIEW[^(]+)(\s+AS\s+WITH\s+.+)`) -var createViewRe = regexp.MustCompile(`(?im)^(CREATE[\s\w]+VIEW[^(]+)(\s+AS\s+SELECT.+)`) +var createViewRe = regexp.MustCompile(`(?im)^(CREATE[\s\w]+VIEW.+?)(\s+AS\s+SELECT.+)`) var attachViewRefreshRe = regexp.MustCompile(`(?im)^(ATTACH[\s\w]+VIEW[^(]+)(\s+REFRESH\s+.+)`) var attachViewToClauseRe = regexp.MustCompile(`(?im)^(ATTACH[\s\w]+VIEW[^(]+)(\s+TO\s+.+)`) var attachViewAsWithRe = regexp.MustCompile(`(?im)^(ATTACH[\s\w]+VIEW[^(]+)(\s+AS\s+WITH\s+.+)`) -var attachViewRe = regexp.MustCompile(`(?im)^(ATTACH[\s\w]+VIEW[^(]+)(\s+AS\s+.+)`) +var attachViewRe = regexp.MustCompile(`(?im)^(ATTACH[\s\w]+VIEW.+?)(\s+AS\s+.+)`) var createObjRe = regexp.MustCompile(`(?is)^(CREATE [^(]+)(\(.+)`) var onClusterRe = regexp.MustCompile(`(?im)\s+ON\s+CLUSTER\s+`) var distributedRE = regexp.MustCompile(`(Distributed)\(([^,]+),([^)]+)\)`) diff --git a/pkg/clickhouse/clickhouse_test.go b/pkg/clickhouse/clickhouse_test.go index abcbc9547..046ae18ff 100644 --- a/pkg/clickhouse/clickhouse_test.go +++ b/pkg/clickhouse/clickhouse_test.go @@ -189,6 +189,13 @@ func TestEnrichQueryWithOnCluster(t *testing.T) { Version: 19000001, ExpectedQuery: "CREATE VIEW feature_data.insert_price_history_it_az UUID '12a40994-ff52-4a14-8f0b-bccdf1a3a4ba' ON CLUSTER 'my_cluster' AS WITH a AS (SELECT {input_timestamp:DateTime} AS entity_timestamp, concat(it.provider, '_', z.name, '_', it.instance_type) AS entity_id_cloud_az_it, it.provider AS cloud, z.name AS availability_zone, it.instance_type AS instance_type, ph.is_spot AS is_spot, CAST(ph.price, 'float') AS price, now() AS created_timestamp, row_number() OVER (PARTITION BY entity_id_cloud_az_it ORDER BY ph.effective_from DESC) AS rn FROM raw_data.price_history AS ph INNER JOIN raw_data.zones AS z ON z.id = ph.availability_zone INNER JOIN raw_data.instance_types AS it ON it.id = ph.instance_type WHERE (ph.effective_to < {input_timestamp:DateTime}) AND z.latest_value AND it.latest_value) SELECT entity_timestamp, entity_id_cloud_az_it, cloud, availability_zone, instance_type, is_spot, price, created_timestamp FROM a WHERE rn = 1 SETTINGS final = 1", }, + { + Name: "OnCluster provided, version >= 19000000, ATTACH VIEW with UUID and column definitions", + Query: "ATTACH LIVE VIEW test.daily_sales_live UUID '8190d585-7a31-4920-808a-be163dc164d8' (`event_date` UInt32, `sku_id` String, `total_sales` Decimal(38, 2)) AS SELECT toYYYYMMDD(create_time) AS event_date, sku_id, sum(total_amount) AS total_sales FROM test.my_table GROUP BY toYYYYMMDD(create_time), sku_id", + OnCluster: "my_cluster", + Version: 19000001, + ExpectedQuery: "ATTACH LIVE VIEW test.daily_sales_live UUID '8190d585-7a31-4920-808a-be163dc164d8' (`event_date` UInt32, `sku_id` String, `total_sales` Decimal(38, 2)) ON CLUSTER 'my_cluster' AS SELECT toYYYYMMDD(create_time) AS event_date, sku_id, sum(total_amount) AS total_sales FROM test.my_table GROUP BY toYYYYMMDD(create_time), sku_id", + }, } for _, tc := range testCases {