3737#include " iceberg/exception.h"
3838#include " iceberg/file_io.h"
3939#include " iceberg/json_internal.h"
40+ #include " iceberg/partition_field.h"
4041#include " iceberg/partition_spec.h"
4142#include " iceberg/result.h"
4243#include " iceberg/schema.h"
4344#include " iceberg/snapshot.h"
4445#include " iceberg/sort_order.h"
4546#include " iceberg/table_properties.h"
4647#include " iceberg/table_update.h"
48+ #include " iceberg/util/checked_cast.h"
4749#include " iceberg/util/error_collector.h"
4850#include " iceberg/util/gzip_internal.h"
4951#include " iceberg/util/location_util.h"
@@ -428,7 +430,8 @@ class TableMetadataBuilder::Impl {
428430 Result<int32_t > AddSortOrder (const SortOrder& order);
429431 Status SetProperties (const std::unordered_map<std::string, std::string>& updated);
430432 Status RemoveProperties (const std::unordered_set<std::string>& removed);
431-
433+ Status SetDefaultPartitionSpec (int32_t spec_id);
434+ Result<int32_t > AddPartitionSpec (const PartitionSpec& spec);
432435 std::unique_ptr<TableMetadata> Build ();
433436
434437 private:
@@ -438,6 +441,12 @@ class TableMetadataBuilder::Impl {
438441 // / \return The ID to use for this sort order (reused if exists, new otherwise)
439442 int32_t ReuseOrCreateNewSortOrderId (const SortOrder& new_order);
440443
444+ // / \brief Internal method to check for existing partition spec and reuse its ID or
445+ // / create a new one
446+ // / \param new_spec The partition spec to check
447+ // / \return The ID to use for this partition spec (reused if exists, new otherwise)
448+ int32_t ReuseOrCreateNewPartitionSpecId (const PartitionSpec& new_spec);
449+
441450 private:
442451 // Base metadata (nullptr for new tables)
443452 const TableMetadata* base_;
@@ -540,9 +549,10 @@ Result<int32_t> TableMetadataBuilder::Impl::AddSortOrder(const SortOrder& order)
540549 bool is_new_order =
541550 last_added_order_id_.has_value () &&
542551 std::ranges::find_if (changes_, [new_order_id](const auto & change) {
543- auto * add_sort_order = dynamic_cast <table::AddSortOrder*>(change.get ());
544- return add_sort_order &&
545- add_sort_order->sort_order ()->order_id () == new_order_id;
552+ return change->kind () == TableUpdate::Kind::kAddSortOrder &&
553+ internal::checked_cast<const table::AddSortOrder&>(*change)
554+ .sort_order ()
555+ ->order_id () == new_order_id;
546556 }) != changes_.cend ();
547557 last_added_order_id_ = is_new_order ? std::make_optional (new_order_id) : std::nullopt ;
548558 return new_order_id;
@@ -572,6 +582,69 @@ Result<int32_t> TableMetadataBuilder::Impl::AddSortOrder(const SortOrder& order)
572582 return new_order_id;
573583}
574584
585+ Status TableMetadataBuilder::Impl::SetDefaultPartitionSpec (int32_t spec_id) {
586+ if (spec_id == -1 ) {
587+ if (!last_added_spec_id_.has_value ()) {
588+ return ValidationFailed (
589+ " Cannot set last added partition spec: no partition spec has been added" );
590+ }
591+ return SetDefaultPartitionSpec (last_added_spec_id_.value ());
592+ }
593+
594+ if (spec_id == metadata_.default_spec_id ) {
595+ // the new spec is already current and no change is needed
596+ return {};
597+ }
598+
599+ metadata_.default_spec_id = spec_id;
600+ if (last_added_spec_id_ == std::make_optional (spec_id)) {
601+ changes_.push_back (std::make_unique<table::SetDefaultPartitionSpec>(kLastAdded ));
602+ } else {
603+ changes_.push_back (std::make_unique<table::SetDefaultPartitionSpec>(spec_id));
604+ }
605+ return {};
606+ }
607+
608+ Result<int32_t > TableMetadataBuilder::Impl::AddPartitionSpec (const PartitionSpec& spec) {
609+ int32_t new_spec_id = ReuseOrCreateNewPartitionSpecId (spec);
610+
611+ if (specs_by_id_.contains (new_spec_id)) {
612+ // update last_added_spec_id if the spec was added in this set of changes (since it
613+ // is now the last)
614+ bool is_new_spec =
615+ last_added_spec_id_.has_value () &&
616+ std::ranges::find_if (changes_, [new_spec_id](const auto & change) {
617+ return change->kind () == TableUpdate::Kind::kAddPartitionSpec &&
618+ internal::checked_cast<const table::AddPartitionSpec&>(*change)
619+ .spec ()
620+ ->spec_id () == new_spec_id;
621+ }) != changes_.cend ();
622+ last_added_spec_id_ = is_new_spec ? std::make_optional (new_spec_id) : std::nullopt ;
623+ return new_spec_id;
624+ }
625+
626+ // Get current schema and validate the partition spec against it
627+ ICEBERG_ASSIGN_OR_RAISE (auto schema, metadata_.Schema ());
628+ ICEBERG_RETURN_UNEXPECTED (spec.Validate (*schema, /* allow_missing_fields=*/ false ));
629+ ICEBERG_CHECK (
630+ metadata_.format_version > 1 || PartitionSpec::HasSequentialFieldIds (spec),
631+ " Spec does not use sequential IDs that are required in v1: {}" , spec.ToString ());
632+
633+ ICEBERG_ASSIGN_OR_RAISE (
634+ std::shared_ptr<PartitionSpec> new_spec,
635+ PartitionSpec::Make (new_spec_id, std::vector<PartitionField>(spec.fields ().begin (),
636+ spec.fields ().end ())));
637+ metadata_.last_partition_id =
638+ std::max (metadata_.last_partition_id , new_spec->last_assigned_field_id ());
639+ metadata_.partition_specs .push_back (new_spec);
640+ specs_by_id_.emplace (new_spec_id, new_spec);
641+
642+ changes_.push_back (std::make_unique<table::AddPartitionSpec>(new_spec));
643+ last_added_spec_id_ = new_spec_id;
644+
645+ return new_spec_id;
646+ }
647+
575648Status TableMetadataBuilder::Impl::SetProperties (
576649 const std::unordered_map<std::string, std::string>& updated) {
577650 // If updated is empty, return early (no-op)
@@ -653,6 +726,20 @@ int32_t TableMetadataBuilder::Impl::ReuseOrCreateNewSortOrderId(
653726 return new_order_id;
654727}
655728
729+ int32_t TableMetadataBuilder::Impl::ReuseOrCreateNewPartitionSpecId (
730+ const PartitionSpec& new_spec) {
731+ // if the spec already exists, use the same ID. otherwise, use the highest ID + 1.
732+ int32_t new_spec_id = PartitionSpec::kInitialSpecId ;
733+ for (const auto & spec : metadata_.partition_specs ) {
734+ if (new_spec.CompatibleWith (*spec)) {
735+ return spec->spec_id ();
736+ } else if (new_spec_id <= spec->spec_id ()) {
737+ new_spec_id = spec->spec_id () + 1 ;
738+ }
739+ }
740+ return new_spec_id;
741+ }
742+
656743TableMetadataBuilder::TableMetadataBuilder (int8_t format_version)
657744 : impl_(std::make_unique<Impl>(format_version)) {}
658745
@@ -723,16 +810,19 @@ TableMetadataBuilder& TableMetadataBuilder::AddSchema(std::shared_ptr<Schema> sc
723810
724811TableMetadataBuilder& TableMetadataBuilder::SetDefaultPartitionSpec (
725812 std::shared_ptr<PartitionSpec> spec) {
726- throw IcebergError (std::format (" {} not implemented" , __FUNCTION__));
813+ ICEBERG_BUILDER_ASSIGN_OR_RETURN (auto spec_id, impl_->AddPartitionSpec (*spec));
814+ return SetDefaultPartitionSpec (spec_id);
727815}
728816
729817TableMetadataBuilder& TableMetadataBuilder::SetDefaultPartitionSpec (int32_t spec_id) {
730- throw IcebergError (std::format (" {} not implemented" , __FUNCTION__));
818+ ICEBERG_BUILDER_RETURN_IF_ERROR (impl_->SetDefaultPartitionSpec (spec_id));
819+ return *this ;
731820}
732821
733822TableMetadataBuilder& TableMetadataBuilder::AddPartitionSpec (
734823 std::shared_ptr<PartitionSpec> spec) {
735- throw IcebergError (std::format (" {} not implemented" , __FUNCTION__));
824+ ICEBERG_BUILDER_ASSIGN_OR_RETURN (auto spec_id, impl_->AddPartitionSpec (*spec));
825+ return *this ;
736826}
737827
738828TableMetadataBuilder& TableMetadataBuilder::RemovePartitionSpecs (
0 commit comments