|
69 | 69 | #include <Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.h> |
70 | 70 | #include <Storages/ObjectStorage/DataLakes/Iceberg/IcebergTableStateSnapshot.h> |
71 | 71 | #include <Storages/ObjectStorage/DataLakes/Iceberg/IcebergWrites.h> |
| 72 | +#include <Storages/ObjectStorage/DataLakes/Iceberg/MetadataGenerator.h> |
72 | 73 | #include <Storages/ObjectStorage/DataLakes/Iceberg/ManifestFile.h> |
73 | 74 | #include <Storages/ObjectStorage/DataLakes/Iceberg/ManifestFilesPruning.h> |
74 | 75 | #include <Storages/ObjectStorage/DataLakes/Iceberg/Mutations.h> |
@@ -110,6 +111,7 @@ extern const int ICEBERG_SPECIFICATION_VIOLATION; |
110 | 111 | extern const int S3_ERROR; |
111 | 112 | extern const int TABLE_ALREADY_EXISTS; |
112 | 113 | extern const int SUPPORT_IS_DISABLED; |
| 114 | +extern const int INCORRECT_DATA; |
113 | 115 | } |
114 | 116 |
|
115 | 117 | namespace Setting |
@@ -311,15 +313,17 @@ void IcebergMetadata::backgroundMetadataPrefetcherThread() |
311 | 313 | /// first, we fetch the latest metadata version and cache it; |
312 | 314 | /// as a part of the same method, we download metadata.json of the latest metadata version |
313 | 315 | /// and after parsing it, we fetch manifest lists, parse and cache them |
314 | | - auto ctx = Context::getGlobalContextInstance()->getBackgroundContext(); |
| 316 | + auto ctx = Context::createCopy(Context::getGlobalContextInstance()); |
315 | 317 | auto [actual_data_snapshot, actual_table_state_snapshot] = getRelevantState(ctx, true); |
316 | 318 | if (actual_data_snapshot) |
317 | 319 | { |
318 | 320 | for (const auto & entry : actual_data_snapshot->manifest_list_entries) |
319 | 321 | { |
320 | 322 | /// second, we fetch, parse and cache each manifest file |
321 | | - auto manifest_file_ptr = getManifestFileEntriesHandle( |
322 | | - object_storage, persistent_components, ctx, log, entry, actual_table_state_snapshot.schema_id); |
| 323 | + auto manifest_file_ptr = Iceberg::getManifestFile( |
| 324 | + object_storage, persistent_components, ctx, log, |
| 325 | + entry.manifest_file_path, |
| 326 | + entry.manifest_file_byte_size); |
323 | 327 | } |
324 | 328 | } |
325 | 329 |
|
@@ -658,6 +662,100 @@ void IcebergMetadata::mutate( |
658 | 662 | ); |
659 | 663 | } |
660 | 664 |
|
| 665 | +void IcebergMetadata::truncate(ContextPtr context, std::shared_ptr<DataLake::ICatalog> catalog, const StorageID & storage_id) |
| 666 | +{ |
| 667 | + if (!context->getSettingsRef()[Setting::allow_insert_into_iceberg].value) |
| 668 | + throw Exception( |
| 669 | + ErrorCodes::SUPPORT_IS_DISABLED, |
| 670 | + "Iceberg truncate requires the allow_insert_into_iceberg setting to be enabled."); |
| 671 | + |
| 672 | + auto [actual_data_snapshot, actual_table_state_snapshot] = getRelevantState(context); |
| 673 | + auto metadata_object = getMetadataJSONObject( |
| 674 | + actual_table_state_snapshot.metadata_file_path, |
| 675 | + object_storage, |
| 676 | + persistent_components.metadata_cache, |
| 677 | + context, |
| 678 | + log, |
| 679 | + persistent_components.metadata_compression_method, |
| 680 | + persistent_components.table_uuid); |
| 681 | + |
| 682 | + // Use -1 as the Iceberg spec sentinel for "no parent snapshot" |
| 683 | + // (distinct from snapshot ID 0 which is a valid snapshot). |
| 684 | + Int64 parent_snapshot_id = actual_table_state_snapshot.snapshot_id.value_or(-1); |
| 685 | + |
| 686 | + auto config_path = persistent_components.table_path; |
| 687 | + if (!config_path.starts_with('/')) config_path = '/' + config_path; |
| 688 | + if (!config_path.ends_with('/')) config_path += "/"; |
| 689 | + |
| 690 | + bool is_transactional = (catalog != nullptr && catalog->isTransactional()); |
| 691 | + |
| 692 | + // Transactional catalogs (e.g. REST) require a fully-qualified blob URI |
| 693 | + // (scheme://bucket/path) so the catalog can resolve the metadata location |
| 694 | + // independently of any local path configuration. Non-transactional catalogs |
| 695 | + // use bare paths relative to the object storage root. |
| 696 | + FileNamesGenerator filename_generator; |
| 697 | + if (is_transactional || context->getSettingsRef()[Setting::write_full_path_in_iceberg_metadata]) |
| 698 | + { |
| 699 | + String location = metadata_object->getValue<String>(Iceberg::f_location); |
| 700 | + if (!location.ends_with("/")) location += "/"; |
| 701 | + filename_generator = FileNamesGenerator( |
| 702 | + location, config_path, is_transactional, |
| 703 | + persistent_components.metadata_compression_method, write_format); |
| 704 | + } |
| 705 | + else |
| 706 | + { |
| 707 | + filename_generator = FileNamesGenerator( |
| 708 | + config_path, config_path, false, |
| 709 | + persistent_components.metadata_compression_method, write_format); |
| 710 | + } |
| 711 | + |
| 712 | + Int32 new_metadata_version = actual_table_state_snapshot.metadata_version + 1; |
| 713 | + filename_generator.setVersion(new_metadata_version); |
| 714 | + |
| 715 | + auto [metadata_name, storage_metadata_name] = filename_generator.generateMetadataName(); |
| 716 | + |
| 717 | + auto [new_snapshot, manifest_list_name, storage_manifest_list_name] = MetadataGenerator(metadata_object).generateNextMetadata( |
| 718 | + filename_generator, metadata_name, parent_snapshot_id, |
| 719 | + /* added_files */ 0, /* added_records */ 0, /* added_files_size */ 0, |
| 720 | + /* num_partitions */ 0, /* added_delete_files */ 0, /* num_deleted_rows */ 0, |
| 721 | + std::nullopt, std::nullopt, /*is_truncate=*/true); |
| 722 | + |
| 723 | + auto write_settings = context->getWriteSettings(); |
| 724 | + auto buf = object_storage->writeObject( |
| 725 | + StoredObject(storage_manifest_list_name), |
| 726 | + WriteMode::Rewrite, std::nullopt, |
| 727 | + DBMS_DEFAULT_BUFFER_SIZE, write_settings); |
| 728 | + |
| 729 | + generateManifestList(filename_generator, metadata_object, object_storage, |
| 730 | + context, {}, new_snapshot, 0, *buf, Iceberg::FileContentType::DATA, /*use_previous_snapshots=*/false); |
| 731 | + buf->finalize(); |
| 732 | + |
| 733 | + String metadata_content = dumpMetadataObjectToString(metadata_object); |
| 734 | + writeMessageToFile(metadata_content, storage_metadata_name, object_storage, |
| 735 | + context, "*", "", persistent_components.metadata_compression_method); |
| 736 | + |
| 737 | + if (catalog) |
| 738 | + { |
| 739 | + // Transactional catalogs require a fully-qualified blob URI so the catalog |
| 740 | + // can resolve the metadata location independently of local path configuration. |
| 741 | + String catalog_filename = metadata_name; |
| 742 | + if (is_transactional) |
| 743 | + { |
| 744 | + // Build full URI from the table's location field (e.g. "s3://bucket/namespace.table") |
| 745 | + // combined with the relative metadata name. |
| 746 | + String location = metadata_object->getValue<String>(Iceberg::f_location); |
| 747 | + if (!location.ends_with("/")) location += "/"; |
| 748 | + catalog_filename = location + metadata_name; |
| 749 | + } |
| 750 | + |
| 751 | + const auto & [namespace_name, table_name] = DataLake::parseTableName(storage_id.getTableName()); |
| 752 | + if (!catalog->updateMetadata(namespace_name, table_name, catalog_filename, new_snapshot)) |
| 753 | + throw Exception(ErrorCodes::INCORRECT_DATA, |
| 754 | + "Failed to commit Iceberg truncate update to catalog."); |
| 755 | + } |
| 756 | +} |
| 757 | + |
| 758 | + |
661 | 759 | void IcebergMetadata::checkMutationIsPossible(const MutationCommands & commands) |
662 | 760 | { |
663 | 761 | for (const auto & command : commands) |
|
0 commit comments