Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Storages/ObjectStorage/StorageObjectStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,14 @@ SchemaCache & StorageObjectStorage::getSchemaCache(const ContextPtr & context, c

void StorageObjectStorage::mutate([[maybe_unused]] const MutationCommands & commands, [[maybe_unused]] ContextPtr context_)
{
/// For datalake tables (e.g. Iceberg), refresh external metadata so that the
/// storage snapshot contains the `datalake_table_state`. Without this the mutation
/// pipeline will hit a `LOGICAL_ERROR` exception in `iterate` when building the read side.
/// Normally `updateExternalDynamicMetadataIfExists` is called by the
/// analyzer/interpreter for `SELECT` and `INSERT` queries, but `InterpreterAlterQuery`
/// does not call it before invoking `mutate`.
updateExternalDynamicMetadataIfExists(context_);

auto metadata_snapshot = getInMemoryMetadataPtr();
auto storage = getStorageID();
configuration->mutate(commands, context_, storage, metadata_snapshot, catalog, format_settings);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Tags: no-fasttest

# Regression test: ALTER TABLE UPDATE on an Iceberg table used to throw a
# LOGICAL_ERROR exception ("Can't extract iceberg table state from storage
# snapshot") when no SELECT or INSERT preceded the mutation in the same server
# lifetime.
# The root cause was that `StorageObjectStorage::mutate` did not call
# `updateExternalDynamicMetadataIfExists` before reading data, so the storage
# snapshot lacked the `datalake_table_state` required by `IcebergMetadata::iterate`.

# Use `IcebergS3` rather than `IcebergLocal`, because the local object storage
# becomes read-only after `ATTACH` (it honours the `is_readonly` flag passed to
# `createObjectStorage`), which would block the mutation. S3 ignores that flag.

CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh

TABLE="t_${CLICKHOUSE_DATABASE}_${RANDOM}"
TABLE_PATH="04092_iceberg_mutate/${CLICKHOUSE_TEST_UNIQUE_NAME}"

${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS ${TABLE}"
${CLICKHOUSE_CLIENT} --query "
CREATE TABLE ${TABLE} (c0 String)
ENGINE = IcebergS3(s3_conn, filename = '${TABLE_PATH}')
"

# Populate the Iceberg data files in S3.
${CLICKHOUSE_CLIENT} --allow_insert_into_iceberg=1 --query "INSERT INTO ${TABLE} VALUES ('a')"

# DETACH + ATTACH rebuilds the storage object from the stored CREATE statement,
# so its in-memory metadata has no `datalake_table_state` loaded — the same
# state as after a server restart. The Iceberg files in S3 are preserved.
${CLICKHOUSE_CLIENT} --query "DETACH TABLE ${TABLE}"
${CLICKHOUSE_CLIENT} --query "ATTACH TABLE ${TABLE}"

# This mutation is the first data-reading operation on the freshly attached
# storage — no prior SELECT or INSERT has called
# `updateExternalDynamicMetadataIfExists`.
# `validate_mutation_query = 0` is required to suppress the validate stage
# of `MutationsInterpreter`, which would otherwise construct an
# `InterpreterSelectQuery` whose constructor calls
# `updateExternalDynamicMetadataIfExists` as a side effect and masks the bug.
# Without the fix, this throws a `LOGICAL_ERROR` exception.
${CLICKHOUSE_CLIENT} --allow_insert_into_iceberg=1 --validate_mutation_query=0 --query "ALTER TABLE ${TABLE} UPDATE c0 = 'b' WHERE TRUE"

${CLICKHOUSE_CLIENT} --query "SELECT c0 FROM ${TABLE}"

${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS ${TABLE}"
Loading