From c2f48c0a5d8556c78ab31c26fab7281165a56886 Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 7 Apr 2026 10:15:46 +0000 Subject: [PATCH] Merge pull request #101577 from groeneai/fix/iceberg-mv-crash-93278 Fix crash when reading Iceberg table through materialized view --- src/Storages/StorageMaterializedView.cpp | 8 ++++ .../04077_iceberg_mv_select_crash.reference | 2 + .../04077_iceberg_mv_select_crash.sh | 40 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 tests/queries/0_stateless/04077_iceberg_mv_select_crash.reference create mode 100755 tests/queries/0_stateless/04077_iceberg_mv_select_crash.sh diff --git a/src/Storages/StorageMaterializedView.cpp b/src/Storages/StorageMaterializedView.cpp index 884c39189e01..ccbbadb11884 100644 --- a/src/Storages/StorageMaterializedView.cpp +++ b/src/Storages/StorageMaterializedView.cpp @@ -363,6 +363,14 @@ void StorageMaterializedView::read( std::tie(storage, lock) = refresher->getAndLockTargetTable(getTargetTableId(), context); } + /// For datalake target tables (e.g. IcebergLocal), we must refresh external metadata + /// before reading. Without this call the storage snapshot will lack the + /// datalake_table_state, causing a LOGICAL_ERROR in iterate(). + /// Normally updateExternalDynamicMetadataIfExists is called by the analyzer/interpreter + /// on the outermost storage, but for materialized views that is the view itself + /// (which is a no-op), not the target table. + storage->updateExternalDynamicMetadataIfExists(context); + auto target_metadata_snapshot = storage->getInMemoryMetadataPtr(); auto target_storage_snapshot = storage->getStorageSnapshot(target_metadata_snapshot, context); diff --git a/tests/queries/0_stateless/04077_iceberg_mv_select_crash.reference b/tests/queries/0_stateless/04077_iceberg_mv_select_crash.reference new file mode 100644 index 000000000000..d705e0b20e93 --- /dev/null +++ b/tests/queries/0_stateless/04077_iceberg_mv_select_crash.reference @@ -0,0 +1,2 @@ +42 +OK diff --git a/tests/queries/0_stateless/04077_iceberg_mv_select_crash.sh b/tests/queries/0_stateless/04077_iceberg_mv_select_crash.sh new file mode 100755 index 000000000000..d18531845589 --- /dev/null +++ b/tests/queries/0_stateless/04077_iceberg_mv_select_crash.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-replicated-database +# Tag no-replicated-database: IcebergLocal is non-replicated; REFRESH MV + replicated database + non-replicated target is rejected. + +# Regression test for https://github.com/ClickHouse/ClickHouse/issues/93278 +# SELECT from a materialized view backed by IcebergLocal engine used to crash +# with "Logical error: Can't extract iceberg table state from storage snapshot". +# The root cause: StorageMaterializedView::read() delegates to the target table +# without calling updateExternalDynamicMetadataIfExists(), so the storage +# snapshot lacks datalake_table_state when IcebergMetadata::iterate() is called. + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +TABLE="t_${CLICKHOUSE_DATABASE}_${RANDOM}" +TABLE_PATH="${USER_FILES_PATH}/${TABLE}/" + +${CLICKHOUSE_CLIENT} --query "DROP VIEW IF EXISTS ${TABLE}_mv" + +# Create a materialized view with IcebergLocal target. +${CLICKHOUSE_CLIENT} --query " + CREATE MATERIALIZED VIEW ${TABLE}_mv + REFRESH AFTER 1 MINUTE + ENGINE = IcebergLocal('${TABLE_PATH}') + AS (SELECT 1::Int32 AS c0) +" + +# This used to crash the server with LOGICAL_ERROR. +${CLICKHOUSE_CLIENT} --query "SELECT * FROM ${TABLE}_mv" + +# Insert data and verify it's readable through the view. +${CLICKHOUSE_CLIENT} --query "INSERT INTO ${TABLE}_mv VALUES (42)" --allow_insert_into_iceberg 1 +${CLICKHOUSE_CLIENT} --query "SELECT c0 FROM ${TABLE}_mv" + +# Clean up. +${CLICKHOUSE_CLIENT} --query "DROP VIEW IF EXISTS ${TABLE}_mv" +rm -rf "${TABLE_PATH}" 2>/dev/null + +echo "OK"