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

Code before:
    if (table_id.hasUUID())
    {
        /// Remove by UUID - this is the most reliable method for Atomic databases
        auto & idx = dependencies.get<TableUUID>();
        idx.erase(table_id.uuid);
    }
    else
    {
        /// 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)
        {

Review comment (bug):
The filter logic in the else branch (lines 448-454) to skip entries with UUIDs prevents proper cleanup. If a table without UUID is being dropped but has a dependency entry with a UUID (which shouldn't happen but could due to a bug), that entry would leak. Consider logging a warning if UUID mismatches are detected, or asserting that all entries in the name-based range have Nil UUIDs.

Code after:
    if (table_id.hasUUID())
    {
        /// Remove by UUID - this is the most reliable method for Atomic databases
        auto & idx = dependencies.get<TableUUID>();
        idx.erase(table_id.uuid);
    }
    else
    {
        /// 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)