Skip to content

Commit 9c10e82

Browse files
groeneaiclaude
andcommitted
Do not lazy-load TimeSeries tables so the cross-database move guard sees them
With lazy_load_tables=1 a TimeSeries table is reloaded as a StorageTableProxy. DatabaseAtomic::renameTable guards a cross-database move with dynamic_cast<StorageTimeSeries *>, which fails on the proxy, so a cross-database RENAME TABLE of a lazy TimeSeries with inner tables was accepted: only the outer table moved and the inner tables were orphaned in the source database. MaterializedView avoids this entire class because shouldLazyLoad already excludes it, so an MV is never a proxy and the sibling guard branch is safe. TimeSeries owns inner tables and overrides renameInMemory the same way, so exclude it from lazy load too. Plain tables keep lazy-loading; only the TimeSeries outer table is loaded eagerly, which the guard can then see. Extends the 04340 regression with a lazy_load_tables=1 cross-database move case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 04e8cb0 commit 9c10e82

3 files changed

Lines changed: 30 additions & 0 deletions

File tree

src/Databases/DatabaseOrdinary.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,11 @@ bool DatabaseOrdinary::shouldLazyLoad(const ASTCreateQuery & query, LoadingStric
427427
|| query.isParameterizedView() || query.is_window_view)
428428
return false;
429429

430+
/// A lazy proxy would hide the TimeSeries type from the cross-database rename guard, so its
431+
/// inner tables could be orphaned by a cross-database move. Load it eagerly, as for views.
432+
if (query.is_time_series_table)
433+
return false;
434+
430435
/// Already handled by `StorageTableFunctionProxy`.
431436
if (query.as_table_function)
432437
return false;

tests/queries/0_stateless/04340_timeseries_rename_digest_mismatch.reference

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ rejected
1010
1
1111
1
1212
1
13+
rejected
14+
3
15+
0
16+
1

tests/queries/0_stateless/04340_timeseries_rename_digest_mismatch.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,24 @@ ${CLICKHOUSE_CLIENT} -q "EXISTS TABLE ${ORD}.\`.inner.metrics.ts\`"
7575
${CLICKHOUSE_CLIENT} -q "EXISTS TABLE ${ORD}.ts"
7676

7777
${CLICKHOUSE_CLIENT} --send_logs_level=fatal -q "DROP DATABASE ${ORD} SYNC" 2>/dev/null || true
78+
79+
# With lazy_load_tables=1 a reloaded TimeSeries table is materialized as a StorageTableProxy. The
80+
# proxy must not hide the TimeSeries type from the cross-database move guard -- otherwise a
81+
# cross-database RENAME would move only the outer table and orphan its inner tables in the old
82+
# database. The outer TimeSeries table is loaded eagerly so the guard sees it.
83+
LZ="${CLICKHOUSE_DATABASE}_lazy"
84+
LZ2="${CLICKHOUSE_DATABASE}_lazy2"
85+
${CLICKHOUSE_CLIENT} -q "DROP DATABASE IF EXISTS ${LZ} SYNC; DROP DATABASE IF EXISTS ${LZ2} SYNC"
86+
${CLICKHOUSE_CLIENT} -q "CREATE DATABASE ${LZ} ENGINE = Atomic SETTINGS lazy_load_tables = 1"
87+
${CLICKHOUSE_CLIENT} -q "CREATE DATABASE ${LZ2} ENGINE = Atomic"
88+
${CLICKHOUSE_CLIENT} --allow_experimental_time_series_table=1 -q "CREATE TABLE ${LZ}.ts ENGINE = TimeSeries"
89+
# Reattach so the table is recreated as a lazy proxy.
90+
${CLICKHOUSE_CLIENT} -q "DETACH DATABASE ${LZ}; ATTACH DATABASE ${LZ}"
91+
# The cross-database move is rejected before any inner table is moved.
92+
${CLICKHOUSE_CLIENT} --send_logs_level=fatal -q "RENAME TABLE ${LZ}.ts TO ${LZ2}.ts" 2>&1 | grep -q -F "Cannot move TimeSeries table with inner tables" && echo "rejected"
93+
# Every inner table stayed in the source database (none orphaned in the target).
94+
${CLICKHOUSE_CLIENT} -q "SELECT count() FROM system.tables WHERE database = '${LZ}' AND name LIKE '.inner_id.%'"
95+
${CLICKHOUSE_CLIENT} -q "SELECT count() FROM system.tables WHERE database = '${LZ2}' AND name LIKE '.inner_id.%'"
96+
${CLICKHOUSE_CLIENT} -q "EXISTS TABLE ${LZ}.ts"
97+
${CLICKHOUSE_CLIENT} --send_logs_level=fatal -q "DROP DATABASE ${LZ} SYNC" 2>/dev/null || true
98+
${CLICKHOUSE_CLIENT} --send_logs_level=fatal -q "DROP DATABASE ${LZ2} SYNC" 2>/dev/null || true

0 commit comments

Comments
 (0)