diff --git a/src/iceberg/catalog/memory/in_memory_catalog.cc b/src/iceberg/catalog/memory/in_memory_catalog.cc index ede316add..35593ed93 100644 --- a/src/iceberg/catalog/memory/in_memory_catalog.cc +++ b/src/iceberg/catalog/memory/in_memory_catalog.cc @@ -518,7 +518,35 @@ Status InMemoryCatalog::DropTable(const TableIdentifier& identifier, bool purge) Status InMemoryCatalog::RenameTable(const TableIdentifier& from, const TableIdentifier& to) { std::unique_lock lock(mutex_); - return NotImplemented("rename table"); + + // Renaming a table to itself is a no-op. + if (from == to) { + return {}; + } + + // Verify the source table exists. + ICEBERG_ASSIGN_OR_RAISE(auto from_exists, root_namespace_->TableExists(from)); + if (!from_exists) { + return NoSuchTable("Source table does not exist: {}", from); + } + + // Verify the destination table does not already exist. + ICEBERG_ASSIGN_OR_RAISE(auto to_exists, root_namespace_->TableExists(to)); + if (to_exists) { + return AlreadyExists("Destination table already exists: {}", to); + } + + // Get the metadata location from the source table. + ICEBERG_ASSIGN_OR_RAISE(auto metadata_location, + root_namespace_->GetTableMetadataLocation(from)); + + // Register the destination with the same metadata location. + ICEBERG_RETURN_UNEXPECTED(root_namespace_->RegisterTable(to, metadata_location)); + + // Unregister the source table. + ICEBERG_RETURN_UNEXPECTED(root_namespace_->UnregisterTable(from)); + + return {}; } Result> InMemoryCatalog::LoadTable( diff --git a/src/iceberg/test/in_memory_catalog_test.cc b/src/iceberg/test/in_memory_catalog_test.cc index 1a65098e6..af6921f1e 100644 --- a/src/iceberg/test/in_memory_catalog_test.cc +++ b/src/iceberg/test/in_memory_catalog_test.cc @@ -305,6 +305,49 @@ TEST_F(InMemoryCatalogTest, DropTable) { EXPECT_THAT(result, IsOk()); } +TEST_F(InMemoryCatalogTest, RenameTable) { + // Create a table first. + TableIdentifier ident{.ns = {}, .name = "t1"}; + auto schema = std::make_shared( + std::vector{SchemaField::MakeRequired(1, "x", int64())}, + /*schema_id=*/1); + auto spec = PartitionSpec::Unpartitioned(); + auto sort_order = SortOrder::Unsorted(); + + ICEBERG_UNWRAP_OR_FAIL( + auto table, catalog_->CreateTable(ident, schema, spec, sort_order, + GenerateTestTableLocation(ident.name), {})); + + // Rename to itself is a no-op. + EXPECT_THAT(catalog_->RenameTable(ident, ident), IsOk()); + + // Rename non-existent source table. + TableIdentifier nonexist{.ns = {}, .name = "nonexist"}; + EXPECT_THAT(catalog_->RenameTable(nonexist, ident), IsError(ErrorKind::kNoSuchTable)); + + // Rename to an existing destination table. + TableIdentifier ident2{.ns = {}, .name = "t2"}; + ICEBERG_UNWRAP_OR_FAIL( + auto table2, catalog_->CreateTable(ident2, schema, spec, sort_order, + GenerateTestTableLocation(ident2.name), {})); + EXPECT_THAT(catalog_->RenameTable(ident, ident2), IsError(ErrorKind::kAlreadyExists)); + + // Drop ident2 to clear the destination, then rename. + EXPECT_THAT(catalog_->DropTable(ident2, /*purge=*/false), IsOk()); + + // Rename ident -> ident2. + TableIdentifier renamed{.ns = {}, .name = "t2"}; + EXPECT_THAT(catalog_->RenameTable(ident, renamed), IsOk()); + EXPECT_THAT(catalog_->TableExists(ident), HasValue(::testing::Eq(false))); + EXPECT_THAT(catalog_->TableExists(renamed), HasValue(::testing::Eq(true))); + + // Load the renamed table to verify it's intact. + auto loaded = catalog_->LoadTable(renamed); + ASSERT_THAT(loaded, IsOk()); + EXPECT_EQ(loaded.value()->name().name, "t2"); + EXPECT_EQ(loaded.value()->uuid(), table->uuid()); +} + TEST_F(InMemoryCatalogTest, Namespace) { Namespace ns{.levels = {"n1", "n2"}}; std::unordered_map properties = {{"prop1", "val1"},