Repository: ClickHouse/ClickHouse
File: src/Common/NamedCollections/NamedCollectionsFactory.cpp (C++)

Code before:
        /// Remove by name, but only if the stored dependencies don't have UUIDs.
        /// If they have UUIDs, they should be removed by UUID, not by name.
        auto & idx = dependencies.get<TableName>();
        auto range = idx.equal_range(std::make_tuple(table_id.database_name, table_id.table_name));

        /// Collect entries to erase - only those without UUIDs
        std::vector<decltype(range.first)> to_erase;
        for (auto it = range.first; it != range.second; ++it)
        {
            if (it->table_id.uuid == UUIDHelpers::Nil)
                to_erase.push_back(it);
        }

        for (auto it : to_erase)
            idx.erase(it);
    }
}

void NamedCollectionFactory::renameDependencies(const StorageID & from_table_id, const StorageID & to_table_id)
{
    std::lock_guard lock(mutex);

    /// For Atomic databases, UUIDs don't change on rename, so nothing to do.
    /// The rename interpreter passes StorageIDs without UUIDs, so we check if the
    /// passed IDs have UUIDs (they won't for rename operations).
    if (from_table_id.hasUUID() || to_table_id.hasUUID())
        return;

    /// For non-Atomic databases (name-based tracking), we need to update the table name.
    /// But we should only update entries that don't have UUIDs - entries with UUIDs
    /// are from Atomic databases and should remain unchanged (UUID-based lookup still works).
    auto & name_idx = dependencies.get<TableName>();
    auto name_range = name_idx.equal_range(std::make_tuple(from_table_id.database_name, from_table_id.table_name));

    /// Collect only entries without UUIDs (non-Atomic database entries).
    /// A single table can depend on several named collections, so we need to save all of them
    /// to re-insert the dependency with the new name.
    std::vector<String> collection_names;
    std::vector<decltype(name_range.first)> to_erase;
    for (auto it = name_range.first; it != name_range.second; ++it)
    {
        if (it->table_id.uuid == UUIDHelpers::Nil)
        {
            collection_names.push_back(it->collection_name);
            to_erase.push_back(it);
        }
    }

    if (to_erase.empty())
        return;


Review comment (bug):
The early return logic for UUID-based rename is incorrect. The comment states 'The rename interpreter passes StorageIDs without UUIDs,' but the condition returns early if *either* ID has a UUID. For Atomic databases, the StorageIDs passed to this function will have UUIDs (as they persist across renames), and this condition will cause the function to return early incorrectly. The logic should verify that both IDs either have or don't have UUIDs, or the comment and implementation need to be reconciled.
```suggestion
    /// For Atomic databases, UUIDs don't change on rename, so if both IDs have UUIDs,
    /// dependencies are tracked by UUID and there is nothing to do here.
    /// The rename interpreter passes StorageIDs without UUIDs for non-Atomic renames.
    if (from_table_id.hasUUID() && to_table_id.hasUUID())
```

Code after:
        /// Remove by name for non-Atomic databases (Ordinary, etc.) which don't have UUIDs.
        /// We only remove entries that have Nil UUIDs - entries with UUIDs belong to Atomic
        /// databases and must be removed via the UUID index (handled in the if branch above).
        auto & idx = dependencies.get<TableName>();
        auto range = idx.equal_range(std::make_tuple(table_id.database_name, table_id.table_name));

        /// Collect entries to erase - only those without UUIDs (non-Atomic database entries)
        std::vector<decltype(range.first)> to_erase;
        for (auto it = range.first; it != range.second; ++it)
        {
            if (it->table_id.uuid == UUIDHelpers::Nil)
                to_erase.push_back(it);
        }

        for (auto it : to_erase)
            idx.erase(it);
    }
}

void NamedCollectionFactory::renameDependencies(const StorageID & from_table_id, const StorageID & to_table_id)
{
    std::lock_guard lock(mutex);

    /// The rename interpreter passes StorageIDs without UUIDs.
    /// If either ID has a UUID, it's not a standard rename operation, so nothing to do.
    /// For Atomic databases, the dependencies are tracked by UUID which doesn't change on rename.
    if (from_table_id.hasUUID() || to_table_id.hasUUID())
        return;

    /// For non-Atomic databases (name-based tracking), we need to update the table name.
    /// But we should only update entries that don't have UUIDs - entries with UUIDs
    /// are from Atomic databases and should remain unchanged (UUID-based lookup still works).
    auto & name_idx = dependencies.get<TableName>();
    auto name_range = name_idx.equal_range(std::make_tuple(from_table_id.database_name, from_table_id.table_name));

    /// Collect only entries without UUIDs (non-Atomic database entries).
    /// A single table can depend on several named collections, so we need to save all of them
    /// to re-insert the dependency with the new name.
    std::vector<String> collection_names;
    std::vector<decltype(name_range.first)> to_erase;
    for (auto it = name_range.first; it != name_range.second; ++it)
    {
        if (it->table_id.uuid == UUIDHelpers::Nil)
        {
            collection_names.push_back(it->collection_name);
            to_erase.push_back(it);
        }
    }

    if (to_erase.empty())
        return;