Thực hiện chức năng DROP TABLE trong MiniOB

Giới thiệu

Vì phiên bản của MiniOB liên tục được cập nhật, nhiều video hướng dẫn trước đây về cách thực hiện lệnh DROP TABLE đã lỗi thời. Bài viết này tổng kết lại quá trình học tập cá nhân nhằm cung cấp một hướng tiếp cận rõ ràng và dễ hiểu cho việc triển khai chức năng này trong hệ thống cơ sở dữ liệu MiniOB.

I. Kiến trúc tổng thể

Luồng thực thi của lệnh DROP TABLE:

  1. Phân tích cú pháp SQL xử lý câu lệnh DROP TABLE.
  2. Trình thực thi gọi logic xóa từ lớp Db.
  3. Lớp Db tìm đối tượng bảng và gọi lớp Table để thực hiện xóa.
  4. Lớp Table tiến hành xóa các tệp chỉ mục và dữ liệu.
  5. Cuối cùng là dọn dẹp tệp meta và các đối tượng bộ nhớ.

II. Triển khai ở tầng phân tích cú pháp

Bước 1: Tạo lớp DropTableStmt

// src/observer/sql/stmt/drop_table_stmt.h
#pragma once

#include "common/lang/string.h"
#include "common/lang/vector.h"
#include "sql/stmt/stmt.h"

class Db;

/**
 * @brief Biểu diễn câu lệnh xóa bảng
 * @ingroup Statement
 */
class DropTableStmt : public Stmt
{
public:
  DropTableStmt(const string &table_name)
      : table_name_(table_name)
  {}
  virtual ~DropTableStmt() = default;

  StmtType type() const override { return StmtType::DROP_TABLE; }

  const string &table_name() const { return table_name_; }

  static RC create(Db *db, const DropTableSqlNode &drop_table, Stmt *&stmt);

private:
  string table_name_;
};
// src/observer/sql/stmt/drop_table_stmt.cpp
#include "common/log/log.h"
#include "common/types.h"
#include "sql/stmt/drop_table_stmt.h"
#include "event/sql_debug.h"

RC DropTableStmt::create(Db *db, const DropTableSqlNode &drop_table, Stmt *&stmt)
{
  stmt = new DropTableStmt(drop_table.relation_name);
  return RC::SUCCESS;
}

Bước 2: Mở rộng trình phân tích lệnh

// src/observer/sql/stmt/stmt.cpp
#include "sql/stmt/drop_table_stmt.h"

RC Stmt::create_stmt(Db *db, const ParsedSqlNode *sql_node, Stmt *&stmt) {
  switch (sql_node->flag) {
    case SCF_DROP_TABLE: {
      return DropTableStmt::create(db, sql_node->sstr.drop_table, stmt);
    }
    // Các trường hợp khác...
  }
}

Điểm quan trọng:

  • Sử dụng SCF_DROP_TABLE để nhận diện loại lệnh.
  • Chuyển đổi cấu trúc SQL thành đối tượng DropTableStmt.
  • Ghi log để hỗ trợ debug.

III. Triển khai ở tầng thực thi

Bước 1: Tạo lớp DropTableExecutor

// src/observer/sql/executor/drop_table_executor.h
class SQLStageEvent;

/**
 * @brief Thực thi lệnh DROP TABLE
 * @ingroup Executor
 */
class DropTableExecutor
{
public:
  DropTableExecutor()          = default;
  virtual ~DropTableExecutor() = default;

  RC execute(SQLStageEvent *sql_event);
};

Bước 2: Đăng ký vào trình thực thi lệnh

// src/observer/sql/executor/drop_table_executor.cpp
RC DropTableExecutor::execute(SQLStageEvent *sql_event)
{
  Stmt    *stmt    = sql_event->stmt();
  Session *session = sql_event->session_event()->session();

  DropTableStmt *drop_table_stmt = static_cast<DropTableStmt *>(stmt);

  const char *table_name = drop_table_stmt->table_name().c_str();
  RC rc = session->get_current_db()->drop_table(table_name);

  return rc;
}

Điểm quan trọng:

  • Lấy database hiện tại từ session.
  • Gọi phương thức drop_table() trong lớp Db.
  • Xử lý lỗi và ghi log chi tiết.

IV. Triển khai ở tầng Db

Mở rộng lớp Db

// src/observer/storage/db/db.h
class Db {
public:
  RC drop_table(const char *table_name);
// src/observer/storage/db/db.cpp
RC Db::drop_table(const char *table_name)
{
  string  table_file_path = table_meta_file(path_.c_str(), table_name);
  Table  *table           = find_table(table_name);
  if (table == nullptr) {
    LOG_ERROR("Table %s not found in opened tables.", table_name);
    return RC::SCHEMA_TABLE_NOT_EXIST;
  }
  LOG_INFO("Starting to drop table %s", table_name);
  table->drop(table_file_path.c_str(), path_.c_str());
  delete table;
  opened_tables_.erase(table_name);
  return RC::SUCCESS;
}

Điểm quan trọng:

  • Kiểm tra sự tồn tại của bảng trước khi xóa.
  • Thứ tự xóa: đối tượng bảng → dữ liệu trong bộ nhớ → tệp meta.
  • Sử dụng unlink() để xóa tệp vật lý.
  • Ghi log cho từng bước thực hiện.

V. Triển khai ở tầng Table (quan trọng)

Mở rộng lớp Table

// src/observer/storage/table/table.h
class Table {
public:
  RC drop(const char* path);
};

RC Table::drop(const char *path, const char *base_dir)
{
  if (engine_) {
        RC rc = engine_->destroy_indexes();
        if (rc != RC::SUCCESS) {
            LOG_ERROR("Failed to destroy indexes for table %s", table_meta_.name());
        }
    }
  engine_.reset(nullptr);  
  if (::remove(path) < 0) {
    return RC::INTERNAL;
  }  
  string data_file = table_data_file(base_dir, table_meta_.name());
  BufferPoolManager &bpm = db_->buffer_pool_manager();

  RC rc = bpm.remove_file(data_file.c_str());
  if (rc != RC::SUCCESS) {
    return rc;
  }
  else {
    LOG_INFO("Successfully removed data file. File name=%s", data_file.c_str());
  }
  
  return RC::SUCCESS;
}

Thứ tự xóa:

  1. Xóa tất cả các tệp chỉ mục (qua engine).
  2. Xóa tệp dữ liệu bảng.
  3. Ghi log cho từng bước.

VI. Hệ thống chỉ mục

Bước 1: Mở rộng interface Index

// src/observer/storage/index/index.h
class Index {
public:
  virtual RC destroy() = 0; // Thêm phương thức hủy
};

Bước 2: Triển khai B+ Tree

// src/observer/storage/index/bplus_tree_index.cpp
RC BplusTreeIndex::destroy() {
  return handler_.destroy(); // Giao tiếp với handler
}
// src/observer/storage/index/bplus_tree_handler.cpp
RC BplusTreeIndex::destroy()
{
  LOG_INFO("Destroying index %s, field: %s", index_meta_.name(), index_meta_.field());
  index_handler_.destroy();
  return RC::SUCCESS;
}

Điểm quan trọng:

  • Việc hủy chỉ mục cần xóa tệp vật lý.
  • Sử dụng BufferPoolManager để xử lý việc xóa tệp.

VII. Quản lý bộ đệm (BufferPoolManager)

Giao diện xóa tệp

// src/observer/storage/buffer/disk_buffer_pool.cpp
RC DiskBufferPool::remove_file()
{
  bp_manager_.remove_file(file_name_.c_str());
  return RC::SUCCESS;
}

RC BufferPoolManager::remove_file(const char *_file_name)
{
  std::string file_name(_file_name);
  RC rc = close_file(file_name.c_str());
  if (rc != RC::SUCCESS) {
    LOG_WARN("Failed to close file %s. rc=%d, but trying to remove it anyway", file_name.c_str(), rc);
  }

  if (::remove(file_name.c_str()) != 0) {
    int err = errno;
    LOG_ERROR("Failed to remove file %s. errno=%d:%s", file_name.c_str(), err, strerror(err));
    return (err == ENOENT) ? RC::FILE_NOT_EXIST : RC::IOERR_CLOSE;
  } else {
    LOG_INFO("Successfully removed file %s.", file_name.c_str());
  }
  return RC::SUCCESS;
}

Điểm quan trọng:

  1. Đóng file nếu có.
  2. Dùng unlink() để xóa tệp.
  3. Ghi log kết quả.

VIII. Tích hợp với engine bảng

Bước 1: Mở rộng interface TableEngine

// src/observer/storage/table/table_engine.h
class TableEngine {
public:
  virtual RC destroy_indexes() = 0; // Giao diện xóa chỉ mục
};

Bước 2: Triển khai HeapTableEngine

// src/observer/storage/table/heap_table_engine.cpp
RC HeapTableEngine::destroy_indexes() {
    RC rc = RC::SUCCESS;
    if (indexes_.empty()) {
        LOG_INFO("No indexes to destroy for table %s", table_meta_->name());
        return rc;
    }
    for (auto index : indexes_) {
        RC tmp_rc = index->destroy();  // Xóa tệp chỉ mục
        if (tmp_rc != RC::SUCCESS) {
            LOG_ERROR("Failed to destroy index %s. rc=%s", index->index_meta().name(), strrc(tmp_rc));
            rc = tmp_rc;
        }
        delete index;
    }
    indexes_.clear();
    return rc;
}

Điểm quan trọng:

  • Lặp qua tất cả chỉ mục và gọi destroy().
  • Tiếp tục xóa các chỉ mục còn lại dù có lỗi.
  • Xóa đối tượng chỉ mục khỏi bộ nhớ.

IX. Tổng kết luồng xử lý

Luồng gọi hoàn chỉnh:

1. SQLParser -> DropTableStmt::create()
2. CommandExecutor -> DropTableExecutor::execute()
3. Db::drop_table()
   │
   ├── Table::drop()
   │   ├── TableEngine::destroy_indexes()
   │   │   ├── BplusTreeIndex::destroy()
   │   │   │   └── BplusTreeHandler::destroy()
   │   │   │       └── BufferPoolManager::remove_file()
   │   │   └── (Xóa đối tượng chỉ mục trong bộ nhớ)
   │   │
   │   └── BufferPoolManager::remove_file()  // Xóa tệp dữ liệu
   │
   ├── (Xóa đối tượng bảng trong bộ nhớ)
   │
   └── unlink(tệp meta của bảng)

Thứ tự dọn dẹp tài nguyên:

  1. Tệp chỉ mục (nhiều tệp).
  2. Tệp dữ liệu bảng.
  3. Đối tượng bảng trong bộ nhớ.
  4. Tệp meta bảng.

Thẻ: MiniOB DROP TABLE SQL parsing database engine storage management

Đăng vào ngày 30 tháng 5 lúc 08:00