Repository: ClickHouse/ClickHouse
File: src/Databases/DatabaseReplicated.cpp (C++)

Code before:
    new_digest -= getMetadataHash(table_id.table_name);
    new_digest += DB::getMetadataHash(table_id.table_name, statement);
    if (txn && !is_recovering)
        txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1));

    DatabaseAtomic::commitAlterTable(table_id, table_metadata_tmp_path, table_metadata_path, statement, query_context);
    tables_metadata_digest = new_digest;

    /// commitCreateTable() commits the txn, so no need to try to attach it to txn
    assertDigest(query_context);
}


bool DatabaseReplicated::canExecuteReplicatedMetadataAlter() const
{
    /// ReplicatedMergeTree may call commitAlterTable from its background threads when executing ALTER_METADATA entries.
    /// It may update the metadata digest (both locally and in ZooKeeper)
    /// before DatabaseReplicatedDDLWorker::initializeReplication() has finished.
    /// We should not update metadata until the database is initialized.
    std::lock_guard lock{ddl_worker_mutex};
    return ddl_worker_initialized && ddl_worker->isCurrentlyActive();
}

StoragePtr DatabaseReplicated::detachTable(ContextPtr local_context, const String & table_name)
{
    /// SYSTEM RESTART REPLICA calls detachTable and then attachTable without updating the digest.
    /// We need to keep tables_metadata_digest consistent with the tables map to prevent
    /// "Digest does not match" errors in concurrent DDL operations.
    std::lock_guard lock{metadata_mutex};
    UInt64 new_digest = tables_metadata_digest;
    if (digest_initialized)
        new_digest -= getMetadataHash(table_name);

    auto table = DatabaseAtomic::detachTable(local_context, table_name);

    if (digest_initialized)
        tables_metadata_digest = new_digest;

    return table;
}

void DatabaseReplicated::attachTable(ContextPtr local_context, const String & table_name, const StoragePtr & table, const String & relative_table_path)
{
    /// Counterpart of the detachTable override: re-add the table's hash to the digest
    /// when the table is re-attached (e.g., after SYSTEM RESTART REPLICA).
    std::lock_guard lock{metadata_mutex};
    DatabaseAtomic::attachTable(local_context, table_name, table, relative_table_path);

    if (digest_initialized)
        tables_metadata_digest += getMetadataHash(table_name);
}

Review comment (suggestion):
In `detachTablePermanently`, it waits for the DB to completely start with `waitDatabaseStarted`.

Should we also wait here? So we don't need `digest_initialized` to synchronize `tables_metadata_digest`.

Code after:
    std::lock_guard lock{metadata_mutex};
    UInt64 new_digest = tables_metadata_digest;
    new_digest -= getMetadataHash(table_id.table_name);
    new_digest += DB::getMetadataHash(table_id.table_name, statement);
    if (txn && !is_recovering)
        txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1));

    DatabaseAtomic::commitAlterTable(table_id, table_metadata_tmp_path, table_metadata_path, statement, query_context);
    tables_metadata_digest = new_digest;

    /// commitCreateTable() commits the txn, so no need to try to attach it to txn
    assertDigest(query_context);
}


bool DatabaseReplicated::canExecuteReplicatedMetadataAlter() const
{
    /// ReplicatedMergeTree may call commitAlterTable from its background threads when executing ALTER_METADATA entries.
    /// It may update the metadata digest (both locally and in ZooKeeper)
    /// before DatabaseReplicatedDDLWorker::initializeReplication() has finished.
    /// We should not update metadata until the database is initialized.
    std::lock_guard lock{ddl_worker_mutex};
    return ddl_worker_initialized && ddl_worker->isCurrentlyActive();
}

void DatabaseReplicated::detachTablePermanently(ContextPtr local_context, const String & table_name)
{
    waitDatabaseStarted();

    if (!local_context->getServerSettings()[ServerSetting::database_replicated_allow_detach_permanently])
        throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "Support for DETACH TABLE PERMANENTLY is disabled");

    auto txn = local_context->getZooKeeperMetadataTransaction();
    assert(!ddl_worker->isCurrentlyActive() || txn);
    if (txn && txn->isInitialQuery())
    {
        /// We have to remove metadata from zookeeper, because we do not distinguish permanently detached tables
        /// from attached tables when recovering replica.
        String metadata_zk_path = zookeeper_path + "/metadata/" + escapeForFileName(table_name);
        txn->addOp(zkutil::makeRemoveRequest(metadata_zk_path, -1));
    }

    std::lock_guard lock{metadata_mutex};
    UInt64 new_digest = tables_metadata_digest;
    new_digest -= getMetadataHash(table_name);
    if (txn && !is_recovering)
        txn->addOp(zkutil::makeSetRequest(replica_path + "/digest", toString(new_digest), -1));

    DatabaseAtomic::detachTablePermanently(local_context, table_name);
    tables_metadata_digest = new_digest;
    assertDigestInTransactionOrInline(local_context, txn);