Skip to content

[WIP] Feature: Support TRUNCATE TABLE for Iceberg engine#1529

Open
il9ue wants to merge 5 commits intoantalya-26.1from
feature/iceberg-truncate
Open

[WIP] Feature: Support TRUNCATE TABLE for Iceberg engine#1529
il9ue wants to merge 5 commits intoantalya-26.1from
feature/iceberg-truncate

Conversation

@il9ue
Copy link

@il9ue il9ue commented Mar 16, 2026

[Feature] Support TRUNCATE TABLE for Iceberg Engine

Overview
As part of the Antalya release, v26.1 needs to natively support the TRUNCATE TABLE command for the Iceberg database engine. Currently, upstream ClickHouse explicitly rejects this operation. As of PR ClickHouse#91713, executing TRUNCATE down-casts to StorageObjectStorage, where it immediately throws an ErrorCodes::NOT_IMPLEMENTED exception for Data Lake engines.

To support standard analytics workflows and testing pipelines without requiring users to DROP and recreate tables (which breaks catalog bindings), implementing a metadata-only truncation is essential.

Proposed Architecture
Unlike a standard MergeTree truncation that physically drops parts from the local disk, Iceberg truncation must be entirely logical. The implementation will leave physical file garbage collection to standard Iceberg maintenance operations and focus strictly on metadata manipulation.

Core Workflow:

  1. Bypass the Upstream Block: Modify StorageObjectStorage::truncate to check data_lake_metadata->supportsTruncate().
  2. Snapshot Generation: Generate a new Iceberg snapshot ID and increment the metadata version (v<N+1>.metadata.json).
  3. Empty Avro Manifest List: The new snapshot cannot simply omit the manifest list. We must use ClickHouse's internal object storage and Avro APIs to generate and write a strictly typed, empty Avro manifest list to object storage.
  4. Metadata Update: Attach the new empty manifest list to the new snapshot, append the snapshot to the snapshots array, and update the snapshot-log and current-snapshot-id.
  5. Catalog Commit: Perform an atomic swap via the ICatalog interface (e.g., REST Catalog) to point the table to the newly generated metadata JSON.

Implementation Details
The required changes span the following internal abstractions:

  • src/Storages/ObjectStorage/StorageObjectStorage.h/cpp: Override truncate and remove the hardcoded throw Exception. Delegate to IDataLakeMetadata.
  • src/Storages/ObjectStorage/DataLakes/IDataLakeMetadata.h: Introduce supportsTruncate() and truncate(ContextPtr, ICatalog) virtual methods.
  • src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.h/cpp: Implement the core truncation logic. Must safely obtain an IObjectStorage write buffer via context->getWriteSettings() to serialize the empty Avro file before committing the JSON metadata.
  • tests/integration/.../test_iceberg_truncate.py: Added Python integration tests. (Note: Stateless SQL tests are intentionally omitted as ClickHouse ENGINE=Iceberg requires an externally initialized catalog to bootstrap, which is handled via PyIceberg in the integration suite).

Acceptance Criteria

  • Executing TRUNCATE TABLE ice_db.my_table succeeds without throwing NOT_IMPLEMENTED.
  • SELECT count() FROM ice_db.my_table returns 0 immediately after truncation.
  • A new v<N+1>.metadata.json is successfully written to the object storage warehouse.
  • The REST Catalog is atomically updated to point to the new metadata version.
  • Cross-Engine Compatibility: A third-party client (PyIceberg) can independently read the truncated table from the REST catalog without encountering schema or metadata corruption errors.
  • CI Coverage: Integration tests successfully spin up a mock MinIO + REST Catalog, bootstrap the table, and validate the truncation logic end-to-end.

This commit introduces native support for the TRUNCATE TABLE command
for the Iceberg database engine. Execution no longer throws a
NOT_IMPLEMENTED exception for DataLake engines.

To align with Iceberg's architectural standards, this is a metadata-only
operation. It creates a new snapshot with an explicitly generated, strictly
typed empty Avro manifest list, increments the metadata version, and
performs an atomic catalog update.

File changes:
- StorageObjectStorage.cpp: Remove hardcoded exception, delegate to data_lake_metadata->truncate().
- IDataLakeMetadata.h: Introduce supportsTruncate() and truncate() virtual methods.
- IcebergMetadata.h/cpp: Implement the Iceberg-specific metadata truncation, empty manifest list generation via MetadataGenerator, and atomic catalog swap.
- tests/integration/: Add PyIceberg integration tests.
- tests/queries/0_stateless/: Add SQL stateless tests.
@github-actions
Copy link

github-actions bot commented Mar 16, 2026

Workflow [PR], commit [c81fc7f]

@il9ue il9ue self-assigned this Mar 16, 2026
Copy link
Collaborator

@arthurpassos arthurpassos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't implemented anything for Iceberg yet, tho it is in my todo list. I left two small comments for now.

I also looked at the transactional model and it looks ok (assuming I understood it correctly).

My understanding of the Iceberg + catalog transactional model is that updating the catalog is the commit marker, and if it fails, the transaction isn't complete even if the new metadata files were already uploaded. Those become orphan and must be ignored. This also implies an Iceberg table should always be read through a catalog if one exists, otherwise it becomes hard to determine the latest metadata snapshot.

I'll read the code more carefully later.

{
throw Exception(ErrorCodes::NOT_IMPLEMENTED,
"Truncate is not supported for data lake engine");
if (isDataLake())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't isDataLake() the same as the above configuration->isDataLakeconfiguration()? see

bool isDataLake() const override { return configuration->isDataLakeConfiguration(); }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

virtual bool supportsParallelInsert() const { return false; }

virtual void modifyFormatSettings(FormatSettings &, const Context &) const {}
virtual void modifyFormatSettings(FormatSettings & /*format_settings*/, const Context & /*local_context*/) const {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not change this method simply to avoid merge conflicts with upstream later on

@il9ue il9ue force-pushed the feature/iceberg-truncate branch from 41eff19 to c1d252e Compare March 17, 2026 05:31
- Addressed Arthur's review comments (removed redundant isDataLake check, reverted IDataLakeMetadata.h signature).
- Removed the stateless SQL test entirely. Iceberg table bootstrapping requires external catalog initialization, which is fully covered by the PyIceberg integration tests.
@il9ue il9ue force-pushed the feature/iceberg-truncate branch from c1d252e to 601250f Compare March 17, 2026 07:21
@il9ue
Copy link
Author

il9ue commented Mar 19, 2026

@arthurpassos
just pushed a quick fix for the integration test. I had accidentally passed the S3 credentials as key-value settings to DataLakeCatalog rather than positional arguments, which caused the UNKNOWN_SETTING crash in the previous run. The Iceberg truncation test should be completely green now.

Looking at the latest CI pipeline, there are a couple of red checks remaining, but they appear to be unrelated infrastructure flakes/upstream regressions:

  • aggregate_functions: Failed due to a runner CPU throttle (Code: 745... SERVER_OVERLOADED).
  • test_storage_delta/test_cdf.py: Failing deep inside the Rust FFI with a delta-kernel-rs panic (Generic delta kernel error: Expected the first commit to have version 2, got None). This seems to be an existing issue with the DeltaLake CDF mock data in this branch and doesn't overlap with the Iceberg metadata changes.
  • (Plus a few intermittent Docker Hub TLS handshake timeouts during environment setup).

Since the core logical truncation architecture is in place and the specific Iceberg integration tests are passing, I believe this is ready for another look whenever you have the time. Let me know if you need any further adjustments!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants