lbug 0.18.0

An in-process property graph database management system built for query speed and scalability
#include "processor/operator/ddl/drop.h"

#include "catalog/catalog.h"
#include "catalog/catalog_entry/index_catalog_entry.h"
#include "catalog/catalog_entry/rel_group_catalog_entry.h"
#include "common/exception/binder.h"
#include "common/string_utils.h"
#include "main/client_context.h"
#include "main/database.h"
#include "main/database_manager.h"
#include "processor/execution_context.h"
#include "storage/buffer_manager/memory_manager.h"
#include "storage/storage_manager.h"
#include "storage/table/node_table.h"
#include "transaction/transaction.h"
#include <format>

using namespace lbug::catalog;
using namespace lbug::common;

namespace lbug {
namespace processor {

void Drop::executeInternal(ExecutionContext* context) {
    auto clientContext = context->clientContext;
    switch (dropInfo.dropType) {
    case DropType::SEQUENCE: {
        dropSequence(clientContext);
    } break;
    case DropType::TABLE: {
        dropTable(clientContext);
    } break;
    case DropType::MACRO: {
        dropMacro(clientContext);
    } break;
    case DropType::GRAPH: {
        dropGraph(clientContext);
    } break;
    case DropType::INDEX: {
        dropIndex(clientContext);
    } break;
    default:
        UNREACHABLE_CODE;
    }
}

void Drop::dropSequence(const main::ClientContext* context) {
    auto catalog = Catalog::Get(*context);
    auto transaction = transaction::Transaction::Get(*context);
    auto memoryManager = storage::MemoryManager::Get(*context);
    if (!catalog->containsSequence(transaction, dropInfo.name)) {
        auto message = std::format("Sequence {} does not exist.", dropInfo.name);
        switch (dropInfo.conflictAction) {
        case ConflictAction::ON_CONFLICT_DO_NOTHING: {
            appendMessage(message, memoryManager);
            return;
        }
        case ConflictAction::ON_CONFLICT_THROW: {
            throw BinderException(message);
        }
        default:
            UNREACHABLE_CODE;
        }
    }
    catalog->dropSequence(transaction, dropInfo.name);
    appendMessage(std::format("Sequence {} has been dropped.", dropInfo.name), memoryManager);
}

void Drop::dropTable(const main::ClientContext* context) {
    auto catalog = Catalog::Get(*context);
    auto transaction = transaction::Transaction::Get(*context);
    auto memoryManager = storage::MemoryManager::Get(*context);
    if (!catalog->containsTable(transaction, dropInfo.name, context->useInternalCatalogEntry())) {
        auto message = std::format("Table {} does not exist.", dropInfo.name);
        switch (dropInfo.conflictAction) {
        case ConflictAction::ON_CONFLICT_DO_NOTHING: {
            appendMessage(message, memoryManager);
            return;
        }
        case ConflictAction::ON_CONFLICT_THROW: {
            throw BinderException(message);
        }
        default:
            UNREACHABLE_CODE;
        }
    }
    auto entry = catalog->getTableCatalogEntry(transaction, dropInfo.name);
    switch (entry->getType()) {
    case CatalogEntryType::NODE_TABLE_ENTRY: {
        for (auto& indexEntry : catalog->getIndexEntries(transaction)) {
            if (indexEntry->getTableID() == entry->getTableID()) {
                if (StringUtils::caseInsensitiveEquals(indexEntry->getIndexType(), "HASH") ||
                    StringUtils::caseInsensitiveEquals(indexEntry->getIndexType(), "ART")) {
                    continue;
                }
                throw BinderException(
                    std::format("Cannot delete node table {} because it is referenced by index {}.",
                        entry->getName(), indexEntry->getIndexName()));
            }
        }
        for (auto& relEntry : catalog->getRelGroupEntries(transaction)) {
            if (relEntry->isParent(entry->getTableID())) {
                throw BinderException(std::format("Cannot delete node table {} because it is "
                                                  "referenced by relationship table {}.",
                    entry->getName(), relEntry->getName()));
            }
        }
    } break;
    case CatalogEntryType::REL_GROUP_ENTRY: {
        // Do nothing
    } break;
    default:
        UNREACHABLE_CODE;
    }
    catalog->dropTableEntryAndIndex(transaction, dropInfo.name);
    appendMessage(std::format("Table {} has been dropped.", dropInfo.name), memoryManager);
}

void Drop::dropMacro(const main::ClientContext* context) {
    auto catalog = Catalog::Get(*context);
    auto transaction = transaction::Transaction::Get(*context);
    auto memoryManager = storage::MemoryManager::Get(*context);
    handleMacroExistence(context);
    catalog->dropMacro(transaction, dropInfo.name);
    appendMessage(std::format("Macro {} has been dropped.", dropInfo.name), memoryManager);
}

void Drop::dropGraph(const main::ClientContext* context) {
    auto dbManager = main::DatabaseManager::Get(*context);
    auto memoryManager = storage::MemoryManager::Get(*context);

    if (StringUtils::caseInsensitiveEquals(dropInfo.name, "main")) {
        throw BinderException{"Cannot drop the main graph."};
    }

    if (!dbManager->hasGraph(dropInfo.name)) {
        auto message = std::format("Graph {} does not exist.", dropInfo.name);
        switch (dropInfo.conflictAction) {
        case ConflictAction::ON_CONFLICT_DO_NOTHING: {
            appendMessage(message, memoryManager);
            return;
        }
        case ConflictAction::ON_CONFLICT_THROW: {
            throw BinderException(message);
        }
        default:
            UNREACHABLE_CODE;
        }
    }

    if (dbManager->hasDefaultGraph() && StringUtils::getUpper(dbManager->getDefaultGraphName()) ==
                                            StringUtils::getUpper(dropInfo.name)) {
        dbManager->clearDefaultGraph();
    }

    dbManager->dropGraph(dropInfo.name, const_cast<main::ClientContext*>(context));
    appendMessage(std::format("Graph {} has been dropped.", dropInfo.name), memoryManager);
}

void Drop::dropIndex(const main::ClientContext* context) {
    auto catalog = Catalog::Get(*context);
    auto transaction = transaction::Transaction::Get(*context);
    auto memoryManager = storage::MemoryManager::Get(*context);
    if (!catalog->containsTable(transaction, dropInfo.name, context->useInternalCatalogEntry())) {
        auto message = std::format("Table {} does not exist.", dropInfo.name);
        switch (dropInfo.conflictAction) {
        case ConflictAction::ON_CONFLICT_DO_NOTHING: {
            appendMessage(message, memoryManager);
            return;
        }
        case ConflictAction::ON_CONFLICT_THROW: {
            throw BinderException(message);
        }
        default:
            UNREACHABLE_CODE;
        }
    }
    auto tableEntry = catalog->getTableCatalogEntry(transaction, dropInfo.name);
    if (tableEntry->getType() != CatalogEntryType::NODE_TABLE_ENTRY) {
        throw BinderException(
            std::format("Table {} is not a node table; cannot drop index.", dropInfo.name));
    }
    auto tableID = tableEntry->getTableID();
    auto storageManager = storage::StorageManager::Get(*context);
    auto* nodeTable = storageManager->getTable(tableID)->ptrCast<storage::NodeTable>();
    // An index may live in the catalog (user-created indexes), in storage only (the default
    // built-in PK hash index), or in both. Drop whichever are present.
    const auto inCatalog = catalog->containsIndex(transaction, tableID, dropInfo.indexName);
    const auto inStorage = nodeTable->getIndex(dropInfo.indexName).has_value();
    if (!inCatalog && !inStorage) {
        auto message =
            std::format("Index {} does not exist in table {}.", dropInfo.indexName, dropInfo.name);
        switch (dropInfo.conflictAction) {
        case ConflictAction::ON_CONFLICT_DO_NOTHING: {
            appendMessage(message, memoryManager);
            return;
        }
        case ConflictAction::ON_CONFLICT_THROW: {
            throw BinderException(message);
        }
        default:
            UNREACHABLE_CODE;
        }
    }
    if (inCatalog) {
        // Marks the IndexCatalogEntry deleted; the commit path emits the WAL drop record.
        catalog->dropIndex(transaction, tableID, dropInfo.indexName);
    }
    if (inStorage) {
        // Removes the in-memory index; its pages are reclaimed on the next checkpoint.
        nodeTable->dropIndex(dropInfo.indexName);
    }
    appendMessage(std::format("Index {} has been dropped.", dropInfo.indexName), memoryManager);
}

void Drop::handleMacroExistence(const main::ClientContext* context) {
    auto catalog = Catalog::Get(*context);
    auto transaction = transaction::Transaction::Get(*context);
    auto memoryManager = storage::MemoryManager::Get(*context);
    if (!catalog->containsMacro(transaction, dropInfo.name)) {
        auto message = std::format("Macro {} does not exist.", dropInfo.name);
        switch (dropInfo.conflictAction) {
        case ConflictAction::ON_CONFLICT_DO_NOTHING: {
            appendMessage(message, memoryManager);
            return;
        }
        case ConflictAction::ON_CONFLICT_THROW: {
            throw BinderException(message);
        }
        default:
            UNREACHABLE_CODE;
        }
    }
}

} // namespace processor
} // namespace lbug