@@ -428,7 +428,8 @@ class TableMetadataBuilder::Impl {
428428 Result<int32_t > AddSortOrder (const SortOrder& order);
429429 Status SetProperties (const std::unordered_map<std::string, std::string>& updated);
430430 Status RemoveProperties (const std::unordered_set<std::string>& removed);
431-
431+ Status SetDefaultPartitionSpec (int32_t spec_id);
432+ Result<int32_t > AddPartitionSpec (const PartitionSpec& spec);
432433 std::unique_ptr<TableMetadata> Build ();
433434
434435 private:
@@ -438,6 +439,12 @@ class TableMetadataBuilder::Impl {
438439 // / \return The ID to use for this sort order (reused if exists, new otherwise)
439440 int32_t ReuseOrCreateNewSortOrderId (const SortOrder& new_order);
440441
442+ // / \brief Internal method to check for existing partition spec and reuse its ID or
443+ // / create a new one
444+ // / \param new_spec The partition spec to check
445+ // / \return The ID to use for this partition spec (reused if exists, new otherwise)
446+ int32_t ReuseOrCreateNewPartitionSpecId (const PartitionSpec& new_spec);
447+
441448 private:
442449 // Base metadata (nullptr for new tables)
443450 const TableMetadata* base_;
@@ -572,6 +579,58 @@ Result<int32_t> TableMetadataBuilder::Impl::AddSortOrder(const SortOrder& order)
572579 return new_order_id;
573580}
574581
582+ Status TableMetadataBuilder::Impl::SetDefaultPartitionSpec (int32_t spec_id) {
583+ if (spec_id == -1 ) {
584+ if (!last_added_spec_id_.has_value ()) {
585+ return InvalidArgument (
586+ " Cannot set last added partition spec: no partition spec has been added" );
587+ }
588+ return SetDefaultPartitionSpec (last_added_spec_id_.value ());
589+ }
590+
591+ if (spec_id == metadata_.default_spec_id ) {
592+ return {};
593+ }
594+
595+ metadata_.default_spec_id = spec_id;
596+
597+ changes_.push_back (std::make_unique<table::SetDefaultPartitionSpec>(spec_id));
598+ return {};
599+ }
600+
601+ Result<int32_t > TableMetadataBuilder::Impl::AddPartitionSpec (const PartitionSpec& spec) {
602+ int32_t new_spec_id = ReuseOrCreateNewPartitionSpecId (spec);
603+
604+ if (specs_by_id_.find (new_spec_id) != specs_by_id_.end ()) {
605+ // update last_added_spec_id if the spec was added in this set of changes (since it
606+ // is now the last)
607+ bool is_new_spec = last_added_spec_id_.has_value () &&
608+ std::ranges::find_if (changes_, [new_spec_id](const auto & change) {
609+ auto * add_spec =
610+ dynamic_cast <table::AddPartitionSpec*>(change.get ());
611+ return add_spec && add_spec->spec ()->spec_id () == new_spec_id;
612+ }) != changes_.cend ();
613+ last_added_spec_id_ = is_new_spec ? std::make_optional (new_spec_id) : std::nullopt ;
614+ return new_spec_id;
615+ }
616+
617+ // Get current schema and validate the partition spec against it
618+ ICEBERG_ASSIGN_OR_RAISE (auto schema, metadata_.Schema ());
619+ ICEBERG_RETURN_UNEXPECTED (spec.Validate (*schema, /* allow_missing_fields=*/ false ));
620+
621+ std::shared_ptr<PartitionSpec> new_spec;
622+ ICEBERG_ASSIGN_OR_RAISE (
623+ new_spec,
624+ PartitionSpec::Make (new_spec_id, std::vector<PartitionField>(spec.fields ().begin (),
625+ spec.fields ().end ())));
626+ metadata_.partition_specs .push_back (new_spec);
627+ specs_by_id_.emplace (new_spec_id, new_spec);
628+
629+ changes_.push_back (std::make_unique<table::AddPartitionSpec>(new_spec));
630+ last_added_spec_id_ = new_spec_id;
631+ return new_spec_id;
632+ }
633+
575634Status TableMetadataBuilder::Impl::SetProperties (
576635 const std::unordered_map<std::string, std::string>& updated) {
577636 // If updated is empty, return early (no-op)
@@ -653,6 +712,20 @@ int32_t TableMetadataBuilder::Impl::ReuseOrCreateNewSortOrderId(
653712 return new_order_id;
654713}
655714
715+ int32_t TableMetadataBuilder::Impl::ReuseOrCreateNewPartitionSpecId (
716+ const PartitionSpec& new_spec) {
717+ // determine the next spec id
718+ int32_t new_spec_id = PartitionSpec::kInitialSpecId ;
719+ for (const auto & spec : metadata_.partition_specs ) {
720+ if (spec->SameSpec (new_spec)) {
721+ return spec->spec_id ();
722+ } else if (new_spec_id <= spec->spec_id ()) {
723+ new_spec_id = spec->spec_id () + 1 ;
724+ }
725+ }
726+ return new_spec_id;
727+ }
728+
656729TableMetadataBuilder::TableMetadataBuilder (int8_t format_version)
657730 : impl_(std::make_unique<Impl>(format_version)) {}
658731
@@ -723,16 +796,19 @@ TableMetadataBuilder& TableMetadataBuilder::AddSchema(std::shared_ptr<Schema> sc
723796
724797TableMetadataBuilder& TableMetadataBuilder::SetDefaultPartitionSpec (
725798 std::shared_ptr<PartitionSpec> spec) {
726- throw IcebergError (std::format (" {} not implemented" , __FUNCTION__));
799+ ICEBERG_BUILDER_ASSIGN_OR_RETURN (auto spec_id, impl_->AddPartitionSpec (*spec));
800+ return SetDefaultPartitionSpec (spec_id);
727801}
728802
729803TableMetadataBuilder& TableMetadataBuilder::SetDefaultPartitionSpec (int32_t spec_id) {
730- throw IcebergError (std::format (" {} not implemented" , __FUNCTION__));
804+ ICEBERG_BUILDER_RETURN_IF_ERROR (impl_->SetDefaultPartitionSpec (spec_id));
805+ return *this ;
731806}
732807
733808TableMetadataBuilder& TableMetadataBuilder::AddPartitionSpec (
734809 std::shared_ptr<PartitionSpec> spec) {
735- throw IcebergError (std::format (" {} not implemented" , __FUNCTION__));
810+ ICEBERG_BUILDER_ASSIGN_OR_RETURN (auto spec_id, impl_->AddPartitionSpec (*spec));
811+ return *this ;
736812}
737813
738814TableMetadataBuilder& TableMetadataBuilder::RemovePartitionSpecs (
0 commit comments