@@ -442,19 +442,61 @@ TEST(TableMetadataBuilderTest, AddSchemaBasic) {
442442 EXPECT_EQ (metadata->last_column_id , 6 );
443443}
444444
445+ TEST (TableMetadataBuilderTest, AddSchemaInvalidColumnIds) {
446+ auto base = CreateBaseMetadata ();
447+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
448+
449+ // Try to add schema with column ID lower than existing last_column_id
450+ auto field1 =
451+ SchemaField::MakeRequired (2 , " duplicate_id" , int64 ()); // ID 2 already exists
452+ auto invalid_schema = std::make_shared<Schema>(std::vector<SchemaField>{field1}, 1 );
453+ builder->AddSchema (invalid_schema);
454+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
455+ // Should still work - AddSchema automatically uses max(existing, highest_in_schema)
456+ ASSERT_EQ (metadata->schemas .size (), 2 );
457+ EXPECT_EQ (metadata->last_column_id , 3 ); // Should remain 3 (from base metadata)
458+ }
459+
460+ TEST (TableMetadataBuilderTest, AddSchemaWithHigherColumnIds) {
461+ auto base = CreateBaseMetadata ();
462+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
463+
464+ // Add schema with higher column IDs
465+ auto field1 = SchemaField::MakeRequired (10 , " high_id1" , int64 ());
466+ auto field2 = SchemaField::MakeRequired (15 , " high_id2" , string ());
467+ auto new_schema = std::make_shared<Schema>(std::vector<SchemaField>{field1, field2}, 1 );
468+ builder->AddSchema (new_schema);
469+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
470+ ASSERT_EQ (metadata->schemas .size (), 2 );
471+ EXPECT_EQ (metadata->last_column_id , 15 ); // Should be updated to highest field ID
472+ }
473+
474+ TEST (TableMetadataBuilderTest, AddSchemaEmptyFields) {
475+ auto base = CreateBaseMetadata ();
476+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
477+
478+ // Add schema with no fields
479+ auto empty_schema = std::make_shared<Schema>(std::vector<SchemaField>{}, 1 );
480+ builder->AddSchema (empty_schema);
481+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
482+ ASSERT_EQ (metadata->schemas .size (), 2 );
483+ EXPECT_EQ (metadata->last_column_id , 3 ); // Should remain unchanged
484+ }
485+
445486// Test SetCurrentSchema
446487TEST (TableMetadataBuilderTest, SetCurrentSchemaBasic) {
447488 auto base = CreateBaseMetadata ();
448489 auto builder = TableMetadataBuilder::BuildFrom (base.get ());
449490
450- // 1. Set current schema by Schema object
491+ // 1. Set current schema by Schema object with explicit last_column_id
451492 auto field1 = SchemaField::MakeRequired (4 , " new_field" , int64 ());
452493 auto new_schema = std::make_shared<Schema>(std::vector<SchemaField>{field1}, 1 );
453494 builder->SetCurrentSchema (new_schema, 4 );
454495 ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
455496 ASSERT_EQ (metadata->schemas .size (), 2 );
456497 EXPECT_EQ (metadata->current_schema_id .value (), 1 );
457498 EXPECT_EQ (metadata->schemas [1 ]->schema_id ().value (), 1 );
499+ EXPECT_EQ (metadata->last_column_id , 4 );
458500
459501 // 2. Set current schema by schema ID
460502 builder = TableMetadataBuilder::BuildFrom (base.get ());
@@ -480,6 +522,32 @@ TEST(TableMetadataBuilderTest, SetCurrentSchemaBasic) {
480522 EXPECT_EQ (metadata->current_schema_id .value (), 0 );
481523}
482524
525+ TEST (TableMetadataBuilderTest, SetCurrentSchemaWithInvalidLastColumnId) {
526+ auto base = CreateBaseMetadata ();
527+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
528+
529+ // Try to set current schema with last_column_id lower than existing
530+ auto field1 = SchemaField::MakeRequired (4 , " new_field" , int64 ());
531+ auto new_schema = std::make_shared<Schema>(std::vector<SchemaField>{field1}, 1 );
532+ builder->SetCurrentSchema (new_schema, 2 ); // 2 < 3 (existing last_column_id)
533+ ASSERT_THAT (builder->Build (), IsError (ErrorKind::kValidationFailed ));
534+ ASSERT_THAT (builder->Build (), HasErrorMessage (" Invalid last column ID" ));
535+ }
536+
537+ TEST (TableMetadataBuilderTest, SetCurrentSchemaUpdatesLastColumnId) {
538+ auto base = CreateBaseMetadata ();
539+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
540+
541+ // Set current schema with higher last_column_id
542+ auto field1 = SchemaField::MakeRequired (4 , " new_field1" , int64 ());
543+ auto field2 = SchemaField::MakeRequired (8 , " new_field2" , string ());
544+ auto new_schema = std::make_shared<Schema>(std::vector<SchemaField>{field1, field2}, 1 );
545+ builder->SetCurrentSchema (new_schema, 10 ); // Higher than field IDs
546+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
547+ EXPECT_EQ (metadata->current_schema_id .value (), 1 );
548+ EXPECT_EQ (metadata->last_column_id , 10 );
549+ }
550+
483551TEST (TableMetadataBuilderTest, SetCurrentSchemaInvalid) {
484552 auto base = CreateBaseMetadata ();
485553
@@ -538,4 +606,146 @@ TEST(TableMetadataBuilderTest, SetCurrentSchemaRebuildsSpecsAndOrders) {
538606 ASSERT_EQ (metadata->sort_orders .size (), 2 );
539607}
540608
609+ // Test RemoveSchemas
610+ TEST (TableMetadataBuilderTest, RemoveSchemasBasic) {
611+ auto base = CreateBaseMetadata ();
612+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
613+
614+ // Add multiple schemas
615+ auto field1 = SchemaField::MakeRequired (4 , " field1" , int64 ());
616+ auto schema1 = std::make_shared<Schema>(std::vector<SchemaField>{field1}, 1 );
617+ auto field2 = SchemaField::MakeRequired (5 , " field2" , float64 ());
618+ auto schema2 = std::make_shared<Schema>(std::vector<SchemaField>{field2}, 2 );
619+ auto field3 = SchemaField::MakeRequired (6 , " field3" , string ());
620+ auto schema3 = std::make_shared<Schema>(std::vector<SchemaField>{field3}, 3 );
621+
622+ builder->AddSchema (schema1);
623+ builder->AddSchema (schema2);
624+ builder->AddSchema (schema3);
625+
626+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
627+ ASSERT_EQ (metadata->schemas .size (), 4 ); // Original + 3 new
628+
629+ // Remove one schema
630+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
631+ builder->RemoveSchemas ({1 });
632+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
633+ ASSERT_EQ (metadata->schemas .size (), 3 );
634+ EXPECT_EQ (metadata->schemas [0 ]->schema_id ().value (), 0 );
635+ EXPECT_EQ (metadata->schemas [1 ]->schema_id ().value (), 2 );
636+ EXPECT_EQ (metadata->schemas [2 ]->schema_id ().value (), 3 );
637+
638+ // Remove multiple schemas
639+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
640+ builder->RemoveSchemas ({2 , 3 });
641+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
642+ ASSERT_EQ (metadata->schemas .size (), 1 );
643+ EXPECT_EQ (metadata->schemas [0 ]->schema_id ().value (), Schema::kInitialSchemaId );
644+ }
645+
646+ TEST (TableMetadataBuilderTest, RemoveSchemasCannotRemoveCurrent) {
647+ auto base = CreateBaseMetadata ();
648+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
649+
650+ // Add a new schema
651+ auto field1 = SchemaField::MakeRequired (4 , " field1" , int64 ());
652+ auto schema1 = std::make_shared<Schema>(std::vector<SchemaField>{field1}, 1 );
653+ builder->AddSchema (schema1);
654+
655+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
656+ ASSERT_EQ (metadata->schemas .size (), 2 );
657+ EXPECT_EQ (metadata->current_schema_id .value (), 0 );
658+
659+ // Try to remove current schema (ID 0)
660+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
661+ builder->RemoveSchemas ({0 });
662+ ASSERT_THAT (builder->Build (), IsError (ErrorKind::kValidationFailed ));
663+ ASSERT_THAT (builder->Build (), HasErrorMessage (" Cannot remove current schema: 0" ));
664+
665+ // Try to remove current schema along with others
666+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
667+ builder->RemoveSchemas ({0 , 1 });
668+ ASSERT_THAT (builder->Build (), IsError (ErrorKind::kValidationFailed ));
669+ ASSERT_THAT (builder->Build (), HasErrorMessage (" Cannot remove current schema: 0" ));
670+ }
671+
672+ TEST (TableMetadataBuilderTest, RemoveSchemasNonExistent) {
673+ auto base = CreateBaseMetadata ();
674+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
675+
676+ // Add one schema
677+ auto field1 = SchemaField::MakeRequired (4 , " field1" , int64 ());
678+ auto schema1 = std::make_shared<Schema>(std::vector<SchemaField>{field1}, 1 );
679+ builder->AddSchema (schema1);
680+
681+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
682+ ASSERT_EQ (metadata->schemas .size (), 2 );
683+
684+ // Try to remove non-existent schema - should be no-op
685+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
686+ builder->RemoveSchemas ({999 });
687+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
688+ ASSERT_EQ (metadata->schemas .size (), 2 );
689+
690+ // Remove mix of existent and non-existent
691+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
692+ builder->RemoveSchemas ({1 , 999 , 888 });
693+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
694+ ASSERT_EQ (metadata->schemas .size (), 1 );
695+ EXPECT_EQ (metadata->schemas [0 ]->schema_id ().value (), Schema::kInitialSchemaId );
696+ }
697+
698+ TEST (TableMetadataBuilderTest, RemoveSchemasEmptySet) {
699+ auto base = CreateBaseMetadata ();
700+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
701+
702+ // Add a schema
703+ auto field1 = SchemaField::MakeRequired (4 , " field1" , int64 ());
704+ auto schema1 = std::make_shared<Schema>(std::vector<SchemaField>{field1}, 1 );
705+ builder->AddSchema (schema1);
706+
707+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
708+ ASSERT_EQ (metadata->schemas .size (), 2 );
709+
710+ // Remove empty set - should be no-op
711+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
712+ builder->RemoveSchemas ({});
713+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
714+ ASSERT_EQ (metadata->schemas .size (), 2 );
715+ }
716+
717+ TEST (TableMetadataBuilderTest, RemoveSchemasAfterSchemaChange) {
718+ auto base = CreateBaseMetadata ();
719+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
720+
721+ // Add multiple schemas
722+ auto field1 = SchemaField::MakeRequired (4 , " field1" , int64 ());
723+ auto schema1 = std::make_shared<Schema>(std::vector<SchemaField>{field1}, 1 );
724+ auto field2 = SchemaField::MakeRequired (5 , " field2" , float64 ());
725+ auto schema2 = std::make_shared<Schema>(std::vector<SchemaField>{field2}, 2 );
726+
727+ builder->AddSchema (schema1);
728+ builder->AddSchema (schema2);
729+ builder->SetCurrentSchema (1 ); // Set schema1 as current
730+
731+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
732+ ASSERT_EQ (metadata->schemas .size (), 3 );
733+ EXPECT_EQ (metadata->current_schema_id .value (), 1 );
734+
735+ // Now remove the old current schema (ID 0)
736+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
737+ builder->RemoveSchemas ({0 });
738+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
739+ ASSERT_EQ (metadata->schemas .size (), 2 );
740+ EXPECT_EQ (metadata->current_schema_id .value (), 1 );
741+ EXPECT_EQ (metadata->schemas [0 ]->schema_id ().value (), 1 );
742+ EXPECT_EQ (metadata->schemas [1 ]->schema_id ().value (), 2 );
743+
744+ // Cannot remove the new current schema
745+ builder = TableMetadataBuilder::BuildFrom (metadata.get ());
746+ builder->RemoveSchemas ({1 });
747+ ASSERT_THAT (builder->Build (), IsError (ErrorKind::kValidationFailed ));
748+ ASSERT_THAT (builder->Build (), HasErrorMessage (" Cannot remove current schema: 1" ));
749+ }
750+
541751} // namespace iceberg
0 commit comments