前言

经过前面两个实验的铺垫,终于到了给数据库系统添加执行查询计划功能的时候了。给定一条 SQL 语句,我们可以将其中的操作符组织为一棵树,树中的每一个父节点都能从子节点获取 tuple 并处理成操作符想要的样子,下图的根节点 \(\pi\) 会输出最终的查询结果。

对于这样一棵树,我们获取查询结果的方式有许多种,包括:迭代模型、物化模型和向量化模型。本次实验使用的是迭代模型,每个节点都会实现一个 Next() 函数,用于向父节点提供一个 tuple。从根节点开始,每个父节点每次向子节点索取一个 tuple 并处理之后输出:

代码实现

实验主要有三个任务:目录表、执行器和用线性探测哈希表重新实现 hash join 执行器,下面会一个个介绍这几个任务的完成过程。

目录表

目录表可以根据 table_oid 或者 table_name 返回表的元数据,其中最重要的一个字段就是 table_,该字段表示一张表,用于查询、插入、修改和删除 tuple:

  1. using table_oid_t = uint32_t;
  2. using column_oid_t = uint32_t;
  3. struct TableMetadata {
  4. TableMetadata(Schema schema, std::string name, std::unique_ptr<TableHeap> &&table, table_oid_t oid)
  5. : schema_(std::move(schema)), name_(std::move(name)), table_(std::move(table)), oid_(oid) {}
  6. Schema schema_;
  7. std::string name_;
  8. std::unique_ptr<TableHeap> table_;
  9. table_oid_t oid_;
  10. };

目录表类 SimpleCatalog 中有三个要求我们实现的方法:CreateTableGetTable(const std::string &table_name)GetTable(table_oid_t table_oid),第一个方法用于创建一个新的表,后面两个方法用于获取表:

  1. class SimpleCatalog {
  2. public:
  3. SimpleCatalog(BufferPoolManager *bpm, LockManager *lock_manager, LogManager *log_manager)
  4. : bpm_{bpm}, lock_manager_{lock_manager}, log_manager_{log_manager} {}
  5. /**
  6. * Create a new table and return its metadata.
  7. * @param txn the transaction in which the table is being created
  8. * @param table_name the name of the new table
  9. * @param schema the schema of the new table
  10. * @return a pointer to the metadata of the new table
  11. */
  12. TableMetadata *CreateTable(Transaction *txn, const std::string &table_name, const Schema &schema) {
  13. BUSTUB_ASSERT(names_.count(table_name) == 0, "Table names should be unique!");
  14. table_oid_t oid = next_table_oid_++;
  15. auto table = std::make_unique<TableHeap>(bpm_, lock_manager_, log_manager_, txn);
  16. tables_[oid] = std::make_unique<TableMetadata>(schema, table_name, std::move(table), oid);
  17. names_[table_name] = oid;
  18. return tables_[oid].get();
  19. }
  20. /** @return table metadata by name */
  21. TableMetadata *GetTable(const std::string &table_name) {
  22. auto it = names_.find(table_name);
  23. if (it == names_.end()) {
  24. throw std::out_of_range("The table name doesn't exist.");
  25. }
  26. return GetTable(it->second);
  27. }
  28. /** @return table metadata by oid */
  29. TableMetadata *GetTable(table_oid_t table_oid) {
  30. auto it = tables_.find(table_oid);
  31. if (it == tables_.end()) {
  32. throw std::out_of_range("The table oid doesn't exist.");
  33. }
  34. return it->second.get();
  35. }
  36. private:
  37. [[maybe_unused]] BufferPoolManager *bpm_;
  38. [[maybe_unused]] LockManager *lock_manager_;
  39. [[maybe_unused]] LogManager *log_manager_;
  40. /** tables_ : table identifiers -> table metadata. Note that tables_ owns all table metadata. */
  41. std::unordered_map<table_oid_t, std::unique_ptr<TableMetadata>> tables_;
  42. /** names_ : table names -> table identifiers */
  43. std::unordered_map<std::string, table_oid_t> names_;
  44. /** The next table identifier to be used. */
  45. std::atomic<table_oid_t> next_table_oid_{0};
  46. };

测试结果如下:

执行器

执行器用于执行查询计划,该实验要求我们实现下述四种执行器:

  • SeqScanExecutor:顺序扫描执行器,遍历表并返回符合查询条件的 tuple,比如 SELECT * FROM tbl_user where id=1 通过该执行器获取查询结果
  • InsertExecutor:插入执行器,向表格中插入任意数量的 tuple,比如 INSERT INTO ybl_user VALUES (1, 'zhiyiYo'), (2, 'zhiyi')
  • HashJoinExecutor:哈希连接执行器,用于内连接查询操作,比如 SELECT u.id, c.class FROM u JOIN c ON u.id = c.uid
  • AggregationExecutor:聚合执行器,用于执行聚合操作,比如 SELECT MIN(grade), MAX(grade) from tbl_user

每个执行器都继承自抽象类 AbstractExecutor ,有两个纯虚函数 Init()Next(Tuple *tuple) 需要实现,其中 Init() 用于初始化执行器,比如需要在 HashJoinExecutorInit() 中对 left table(outer table) 创建哈希表。AbstractExecutor 还有一个 ExecutorContext 成员,包含一些查询的元数据,比如 BufferPoolManager 和上个任务实现的 SimpleCatalog

  1. class AbstractExecutor {
  2. public:
  3. /**
  4. * Constructs a new AbstractExecutor.
  5. * @param exec_ctx the executor context that the executor runs with
  6. */
  7. explicit AbstractExecutor(ExecutorContext *exec_ctx) : exec_ctx_{exec_ctx} {}
  8. /** Virtual destructor. */
  9. virtual ~AbstractExecutor() = default;
  10. /**
  11. * Initializes this executor.
  12. * @warning This function must be called before Next() is called!
  13. */
  14. virtual void Init() = 0;
  15. /**
  16. * Produces the next tuple from this executor.
  17. * @param[out] tuple the next tuple produced by this executor
  18. * @return true if a tuple was produced, false if there are no more tuples
  19. */
  20. virtual bool Next(Tuple *tuple) = 0;
  21. /** @return the schema of the tuples that this executor produces */
  22. virtual const Schema *GetOutputSchema() = 0;
  23. /** @return the executor context in which this executor runs */
  24. ExecutorContext *GetExecutorContext() { return exec_ctx_; }
  25. protected:
  26. ExecutorContext *exec_ctx_;
  27. };

执行器内部会有一个代表执行计划的 AbstractPlanNode 的子类数据成员,而这些子类内部又会有一个 AbstractExpression 的子类数据成员用于判断查询条件是否成立等操作。

顺序扫描

提供的代码中为我们实现了一个 TableIterator 类,用于迭代 TableHeap,我们只要在 Next 函数中判断迭代器所指的 tuple 是否满足查询条件并递增迭代器,如果满足条件就返回该 tuple,不满足就接着迭代:

  1. class SeqScanExecutor : public AbstractExecutor {
  2. public:
  3. /**
  4. * Creates a new sequential scan executor.
  5. * @param exec_ctx the executor context
  6. * @param plan the sequential scan plan to be executed
  7. */
  8. SeqScanExecutor(ExecutorContext *exec_ctx, const SeqScanPlanNode *plan);
  9. void Init() override;
  10. bool Next(Tuple *tuple) override;
  11. const Schema *GetOutputSchema() override { return plan_->OutputSchema(); }
  12. private:
  13. /** The sequential scan plan node to be executed. */
  14. const SeqScanPlanNode *plan_;
  15. TableMetadata *table_metadata_;
  16. TableIterator table_iterator_;
  17. };

实现代码如下:

  1. SeqScanExecutor::SeqScanExecutor(ExecutorContext *exec_ctx, const SeqScanPlanNode *plan)
  2. : AbstractExecutor(exec_ctx),
  3. plan_(plan),
  4. table_metadata_(exec_ctx->GetCatalog()->GetTable(plan->GetTableOid())),
  5. table_iterator_(table_metadata_->table_->Begin(exec_ctx->GetTransaction())) {}
  6. void SeqScanExecutor::Init() {}
  7. bool SeqScanExecutor::Next(Tuple *tuple) {
  8. auto predicate = plan_->GetPredicate();
  9. while (table_iterator_ != table_metadata_->table_->End()) {
  10. *tuple = *table_iterator_++;
  11. if (!predicate || predicate->Evaluate(tuple, &table_metadata_->schema_).GetAs<bool>()) {
  12. return true;
  13. }
  14. }
  15. return false;
  16. }

插入

插入操作分为两种:

  • raw inserts:插入数据直接来自插入执行器本身,比如 INSERT INTO tbl_user VALUES (1, 15), (2, 16)
  • not-raw inserts:插入的数据来自子执行器,比如 INSERT INTO tbl_user1 SELECT * FROM tbl_user2

可以根据插入计划的 IsRawInsert() 判断插入操作的类型,这个函数根据子查询器列表是否为空进行判断:

  1. /** @return true if we embed insert values directly into the plan, false if we have a child plan providing tuples */
  2. bool IsRawInsert() const { return GetChildren().empty(); }

如果是 raw inserts,我们直接根据插入执行器中的数据构造 tuple 并插入表中,否则调用子执行器的 Next 函数获取数据并插入表中:

  1. class InsertExecutor : public AbstractExecutor {
  2. public:
  3. /**
  4. * Creates a new insert executor.
  5. * @param exec_ctx the executor context
  6. * @param plan the insert plan to be executed
  7. * @param child_executor the child executor to obtain insert values from, can be nullptr
  8. */
  9. InsertExecutor(ExecutorContext *exec_ctx, const InsertPlanNode *plan,
  10. std::unique_ptr<AbstractExecutor> &&child_executor);
  11. const Schema *GetOutputSchema() override;
  12. void Init() override;
  13. // Note that Insert does not make use of the tuple pointer being passed in.
  14. // We return false if the insert failed for any reason, and return true if all inserts succeeded.
  15. bool Next([[maybe_unused]] Tuple *tuple) override;
  16. private:
  17. /** The insert plan node to be executed. */
  18. const InsertPlanNode *plan_;
  19. std::unique_ptr<AbstractExecutor> child_executor_;
  20. TableMetadata *table_metadata_;
  21. };

实现代码为:

  1. InsertExecutor::InsertExecutor(ExecutorContext *exec_ctx, const InsertPlanNode *plan,
  2. std::unique_ptr<AbstractExecutor> &&child_executor)
  3. : AbstractExecutor(exec_ctx),
  4. plan_(plan),
  5. child_executor_(std::move(child_executor)),
  6. table_metadata_(exec_ctx->GetCatalog()->GetTable(plan->TableOid())) {}
  7. const Schema *InsertExecutor::GetOutputSchema() { return plan_->OutputSchema(); }
  8. void InsertExecutor::Init() {}
  9. bool InsertExecutor::Next([[maybe_unused]] Tuple *tuple) {
  10. RID rid;
  11. if (plan_->IsRawInsert()) {
  12. for (const auto &values : plan_->RawValues()) {
  13. Tuple tuple(values, &table_metadata_->schema_);
  14. if (!table_metadata_->table_->InsertTuple(tuple, &rid, exec_ctx_->GetTransaction())) {
  15. return false;
  16. };
  17. }
  18. } else {
  19. Tuple tuple;
  20. while (child_executor_->Next(&tuple)) {
  21. if (!table_metadata_->table_->InsertTuple(tuple, &rid, exec_ctx_->GetTransaction())) {
  22. return false;
  23. };
  24. }
  25. }
  26. return true;
  27. }

哈希连接

哈希连接执行器使用的是最基本的哈希连接算法,没有使用布隆过滤器等优化措施。该算法分为两个阶段:

  1. 将 left table 的 join 语句中各个条件所在列的值作为键,tuple 或者 row id 作为值构造哈希表,这一步允许将相同哈希值的 tuple 插入哈希表
  2. 对 right table 的 join 语句中各个条件所在列的值作为键,在哈希表中进行查询获取所以系统哈希值的 left table 中的 tuple,再使用 join 条件进行精确匹配

对 tuple 进行哈希的函数为:

  1. /**
  2. * Hashes a tuple by evaluating it against every expression on the given schema, combining all non-null hashes.
  3. * @param tuple tuple to be hashed
  4. * @param schema schema to evaluate the tuple on
  5. * @param exprs expressions to evaluate the tuple with
  6. * @return the hashed tuple
  7. */
  8. hash_t HashJoinExecutor::HashValues(const Tuple *tuple, const Schema *schema, const std::vector<const AbstractExpression *> &exprs) {
  9. hash_t curr_hash = 0;
  10. // For every expression,
  11. for (const auto &expr : exprs) {
  12. // We evaluate the tuple on the expression and schema.
  13. Value val = expr->Evaluate(tuple, schema);
  14. // If this produces a value,
  15. if (!val.IsNull()) {
  16. // We combine the hash of that value into our current hash.
  17. curr_hash = HashUtil::CombineHashes(curr_hash, HashUtil::HashValue(&val));
  18. }
  19. }
  20. return curr_hash;
  21. }

为了方便我们的测试,实验提供了一个简易的哈希表 SimpleHashJoinHashTable 用于插入 (hash, tuple) 键值对,该哈希表直接整个放入内存中,如果 tuple 很多,内存会放不下这个哈希表,所以任务三会替换为上一个实验中实现的 LinearProbeHashTable

  1. using HT = SimpleHashJoinHashTable;
  2. class HashJoinExecutor : public AbstractExecutor {
  3. public:
  4. /**
  5. * Creates a new hash join executor.
  6. * @param exec_ctx the context that the hash join should be performed in
  7. * @param plan the hash join plan node
  8. * @param left the left child, used by convention to build the hash table
  9. * @param right the right child, used by convention to probe the hash table
  10. */
  11. HashJoinExecutor(ExecutorContext *exec_ctx, const HashJoinPlanNode *plan, std::unique_ptr<AbstractExecutor> &&left,
  12. std::unique_ptr<AbstractExecutor> &&right);
  13. /** @return the JHT in use. Do not modify this function, otherwise you will get a zero. */
  14. const HT *GetJHT() const { return &jht_; }
  15. const Schema *GetOutputSchema() override { return plan_->OutputSchema(); }
  16. void Init() override;
  17. bool Next(Tuple *tuple) override;
  18. hash_t HashValues(const Tuple *tuple, const Schema *schema, const std::vector<const AbstractExpression *> &exprs) { // 省略 }
  19. private:
  20. /** The hash join plan node. */
  21. const HashJoinPlanNode *plan_;
  22. std::unique_ptr<AbstractExecutor> left_executor_;
  23. std::unique_ptr<AbstractExecutor> right_executor_;
  24. /** The comparator is used to compare hashes. */
  25. [[maybe_unused]] HashComparator jht_comp_{};
  26. /** The identity hash function. */
  27. IdentityHashFunction jht_hash_fn_{};
  28. /** The hash table that we are using. */
  29. HT jht_;
  30. /** The number of buckets in the hash table. */
  31. static constexpr uint32_t jht_num_buckets_ = 2;
  32. };

根据上述的算法过程可以得到实现代码为:

  1. HashJoinExecutor::HashJoinExecutor(ExecutorContext *exec_ctx, const HashJoinPlanNode *plan,
  2. std::unique_ptr<AbstractExecutor> &&left, std::unique_ptr<AbstractExecutor> &&right)
  3. : AbstractExecutor(exec_ctx),
  4. plan_(plan),
  5. left_executor_(std::move(left)),
  6. right_executor_(std::move(right)),
  7. jht_("join hash table", exec_ctx->GetBufferPoolManager(), jht_comp_, jht_num_buckets_, jht_hash_fn_) {}
  8. void HashJoinExecutor::Init() {
  9. left_executor_->Init();
  10. right_executor_->Init();
  11. // create hash table for left child
  12. Tuple tuple;
  13. while (left_executor_->Next(&tuple)) {
  14. auto h = HashValues(&tuple, left_executor_->GetOutputSchema(), plan_->GetLeftKeys());
  15. jht_.Insert(exec_ctx_->GetTransaction(), h, tuple);
  16. }
  17. }
  18. bool HashJoinExecutor::Next(Tuple *tuple) {
  19. auto predicate = plan_->Predicate();
  20. auto left_schema = left_executor_->GetOutputSchema();
  21. auto right_schema = right_executor_->GetOutputSchema();
  22. auto out_schema = GetOutputSchema();
  23. Tuple right_tuple;
  24. while (right_executor_->Next(&right_tuple)) {
  25. // get all tuples with the same hash values in left child
  26. auto h = HashValues(&right_tuple, right_executor_->GetOutputSchema(), plan_->GetRightKeys());
  27. std::vector<Tuple> left_tuples;
  28. jht_.GetValue(exec_ctx_->GetTransaction(), h, &left_tuples);
  29. // get the exact matching left tuple
  30. for (auto &left_tuple : left_tuples) {
  31. if (!predicate || predicate->EvaluateJoin(&left_tuple, left_schema, &right_tuple, right_schema).GetAs<bool>()) {
  32. // create output tuple
  33. std::vector<Value> values;
  34. for (uint32_t i = 0; i < out_schema->GetColumnCount(); ++i) {
  35. auto expr = out_schema->GetColumn(i).GetExpr();
  36. values.push_back(expr->EvaluateJoin(&left_tuple, left_schema, &right_tuple, right_schema));
  37. }
  38. *tuple = Tuple(values, out_schema);
  39. return true;
  40. }
  41. }
  42. }
  43. return false;
  44. }

聚合

聚合执行器内部维护了一个哈希表 SimpleAggregationHashTable 以及哈希表迭代器 aht_iterator_,将键值对插入哈希表的时候会立刻更新聚合结果,最终的查询结果也从该哈希表获取:

  1. AggregationExecutor::AggregationExecutor(ExecutorContext *exec_ctx, const AggregationPlanNode *plan,
  2. std::unique_ptr<AbstractExecutor> &&child)
  3. : AbstractExecutor(exec_ctx),
  4. plan_(plan),
  5. child_(std::move(child)),
  6. aht_(plan->GetAggregates(), plan->GetAggregateTypes()),
  7. aht_iterator_(aht_.Begin()) {}
  8. const AbstractExecutor *AggregationExecutor::GetChildExecutor() const { return child_.get(); }
  9. const Schema *AggregationExecutor::GetOutputSchema() { return plan_->OutputSchema(); }
  10. void AggregationExecutor::Init() {
  11. child_->Init();
  12. // initialize aggregation hash table
  13. Tuple tuple;
  14. while (child_->Next(&tuple)) {
  15. aht_.InsertCombine(MakeKey(&tuple), MakeVal(&tuple));
  16. }
  17. aht_iterator_ = aht_.Begin();
  18. }
  19. bool AggregationExecutor::Next(Tuple *tuple) {
  20. auto having = plan_->GetHaving();
  21. auto out_schema = GetOutputSchema();
  22. while (aht_iterator_ != aht_.End()) {
  23. auto group_bys = aht_iterator_.Key().group_bys_;
  24. auto aggregates = aht_iterator_.Val().aggregates_;
  25. if (!having || having->EvaluateAggregate(group_bys, aggregates).GetAs<bool>()) {
  26. std::vector<Value> values;
  27. for (uint32_t i = 0; i < out_schema->GetColumnCount(); ++i) {
  28. auto expr = out_schema->GetColumn(i).GetExpr();
  29. values.push_back(expr->EvaluateAggregate(group_bys, aggregates));
  30. }
  31. *tuple = Tuple(values, out_schema);
  32. ++aht_iterator_;
  33. return true;
  34. }
  35. ++aht_iterator_;
  36. }
  37. return false;
  38. }

测试

测试结果如下图所示,成功通过所有测试用例:

线性探测哈希表

这个任务要求将哈希连接中的 SimpleHashJoinHashTable 更换成 LinearProbeHashTable,这样就能在磁盘中保存 left table 的哈希表。实验还提示我们可以实现 TmpTuplePage,用于保存 left table 的 tuple,其实我们完全可以用代码中写好的 TablePage 来实现该目的,但是 TmpTuplePage 结构更为精简,可以搭配 Tuple::DeserializeFrom(const char *storage) 食用,通过实现 TmpTuplePage,我们也能加深对 tuple 存储方式的理解。

TmpTuplePage 的格式如下所示:

  1. ---------------------------------------------------------------------------------------------------------
  2. | PageId (4) | LSN (4) | FreeSpace (4) | (free space) | TupleSize2 | TupleData2 | TupleSize1 | TupleData1 |
  3. ---------------------------------------------------------------------------------------------------------
  4. \-----------------V------------------/ ^
  5. header free space pointer

前 12 个字节是 header,记录了 page id、lsn 和 free space pointer,此处的 free space pointer 是相对 page id 的地址而言的。如果表中一个 tuple 都没有,且表大小为 PAGE_SIZE,那么 free space pointer 的值就是 PAGE_SIZE。tuple 从末尾开始插入,每个 tuple 后面跟着 tuple 的大小(占用 4 字节),也就是说插入一个 tuple 占用的空间大小为 tuple.size_ + 4

理解上述内容后,实现 TmpTupleHeader 就很简单了,模仿 TablePage 的写法即可(需要将 TmpTuplePage 声明为 Tuple 的友元):

  1. class TmpTuplePage : public Page {
  2. public:
  3. void Init(page_id_t page_id, uint32_t page_size) {
  4. memcpy(GetData(), &page_id, sizeof(page_id));
  5. SetFreeSpacePointer(page_size);
  6. }
  7. /** @return the page ID of this temp table page */
  8. page_id_t GetTablePageId() { return *reinterpret_cast<page_id_t *>(GetData()); }
  9. bool Insert(const Tuple &tuple, TmpTuple *out) {
  10. // determine whether there is enough space to insert tuple
  11. if (GetFreeSpaceRemaining() < tuple.size_ + SIZE_TUPLE) {
  12. return false;
  13. }
  14. // insert tuple and its size
  15. SetFreeSpacePointer(GetFreeSpacePointer() - tuple.size_);
  16. memcpy(GetData() + GetFreeSpacePointer(), tuple.data_, tuple.size_);
  17. SetFreeSpacePointer(GetFreeSpacePointer() - SIZE_TUPLE);
  18. memcpy(GetData() + GetFreeSpacePointer(), &tuple.size_, SIZE_TUPLE);
  19. out->SetPageId(GetPageId());
  20. out->SetOffset(GetFreeSpacePointer());
  21. return true;
  22. }
  23. private:
  24. static_assert(sizeof(page_id_t) == 4);
  25. static constexpr size_t SIZE_TABLE_PAGE_HEADER = 12;
  26. static constexpr size_t SIZE_TUPLE = 4;
  27. static constexpr size_t OFFSET_FREE_SPACE = 8;
  28. /** @return pointer to the end of the current free space, see header comment */
  29. uint32_t GetFreeSpacePointer() { return *reinterpret_cast<uint32_t *>(GetData() + OFFSET_FREE_SPACE); }
  30. /** set the pointer of the end of current free space.
  31. * @param free_space_ptr the pointer relative to data_
  32. */
  33. void SetFreeSpacePointer(uint32_t free_space_ptr) {
  34. memcpy(GetData() + OFFSET_FREE_SPACE, &free_space_ptr, sizeof(uint32_t));
  35. }
  36. /** @return the size of free space */
  37. uint32_t GetFreeSpaceRemaining() { return GetFreeSpacePointer() - SIZE_TABLE_PAGE_HEADER; }
  38. };

Insert 函数中更新了 TmpTuple 的参数,我们会将 TmpTuple 作为 left table 哈希表的值,而 tuple 放在 TmpTuplePage 中,根据 TmpTuple 中保存的 offset 获取 tuple:

  1. class TmpTuple {
  2. public:
  3. TmpTuple(page_id_t page_id, size_t offset) : page_id_(page_id), offset_(offset) {}
  4. inline bool operator==(const TmpTuple &rhs) const { return page_id_ == rhs.page_id_ && offset_ == rhs.offset_; }
  5. page_id_t GetPageId() const { return page_id_; }
  6. size_t GetOffset() const { return offset_; }
  7. void SetPageId(page_id_t page_id) { page_id_ = page_id; }
  8. void SetOffset(size_t offset) { offset_ = offset; }
  9. private:
  10. page_id_t page_id_;
  11. size_t offset_;
  12. };

接着需要将哈希表更换为 LinearProbeHashTable,在 linear_probe_hash_table.cpp 中需要进行模板特例化:

  1. template class LinearProbeHashTable<hash_t, TmpTuple, HashComparator>;

还要对 HashTableBlockPage 进行模板特例化:

  1. template class HashTableBlockPage<hash_t, TmpTuple, HashComparator>;

接着更改 HT

  1. using HashJoinKeyType = hash_t;
  2. using HashJoinValType = TmpTuple;
  3. using HT = LinearProbeHashTable<HashJoinKeyType, HashJoinValType, HashComparator>;

由于 tuple 可能很多,将 jht_num_buckets_ 设置为 1000 可以减少调整大小的次数,最后是实现代码:

  1. void HashJoinExecutor::Init() {
  2. left_executor_->Init();
  3. right_executor_->Init();
  4. // create temp tuple page
  5. auto buffer_pool_manager = exec_ctx_->GetBufferPoolManager();
  6. page_id_t tmp_page_id;
  7. auto tmp_page = reinterpret_cast<TmpTuplePage *>(buffer_pool_manager->NewPage(&tmp_page_id)->GetData());
  8. tmp_page->Init(tmp_page_id, PAGE_SIZE);
  9. // create hash table for left child
  10. Tuple tuple;
  11. TmpTuple tmp_tuple(tmp_page_id, 0);
  12. while (left_executor_->Next(&tuple)) {
  13. auto h = HashValues(&tuple, left_executor_->GetOutputSchema(), plan_->GetLeftKeys());
  14. // insert tuple to page, creata a new temp tuple page if page if full
  15. if (!tmp_page->Insert(tuple, &tmp_tuple)) {
  16. buffer_pool_manager->UnpinPage(tmp_page_id, true);
  17. tmp_page = reinterpret_cast<TmpTuplePage *>(buffer_pool_manager->NewPage(&tmp_page_id)->GetData());
  18. tmp_page->Init(tmp_page_id, PAGE_SIZE);
  19. // try inserting tuple to page again
  20. tmp_page->Insert(tuple, &tmp_tuple);
  21. }
  22. jht_.Insert(exec_ctx_->GetTransaction(), h, tmp_tuple);
  23. }
  24. buffer_pool_manager->UnpinPage(tmp_page_id, true);
  25. }
  26. bool HashJoinExecutor::Next(Tuple *tuple) {
  27. auto buffer_pool_manager = exec_ctx_->GetBufferPoolManager();
  28. auto left_schema = left_executor_->GetOutputSchema();
  29. auto right_schema = right_executor_->GetOutputSchema();
  30. auto predicate = plan_->Predicate();
  31. auto out_schema = GetOutputSchema();
  32. Tuple right_tuple;
  33. while (right_executor_->Next(&right_tuple)) {
  34. // get all tuples with the same hash values in left child
  35. auto h = HashValues(&right_tuple, right_executor_->GetOutputSchema(), plan_->GetRightKeys());
  36. std::vector<TmpTuple> tmp_tuples;
  37. jht_.GetValue(exec_ctx_->GetTransaction(), h, &tmp_tuples);
  38. // get the exact matching left tuple
  39. for (auto &tmp_tuple : tmp_tuples) {
  40. // convert tmp tuple to left tuple
  41. auto page_id = tmp_tuple.GetPageId();
  42. auto tmp_page = buffer_pool_manager->FetchPage(page_id);
  43. Tuple left_tuple;
  44. left_tuple.DeserializeFrom(tmp_page->GetData() + tmp_tuple.GetOffset());
  45. buffer_pool_manager->UnpinPage(page_id, false);
  46. if (!predicate || predicate->EvaluateJoin(&left_tuple, left_schema, &right_tuple, right_schema).GetAs<bool>()) {
  47. // create output tuple
  48. std::vector<Value> values;
  49. for (uint32_t i = 0; i < out_schema->GetColumnCount(); ++i) {
  50. auto expr = out_schema->GetColumn(i).GetExpr();
  51. values.push_back(expr->EvaluateJoin(&left_tuple, left_schema, &right_tuple, right_schema));
  52. }
  53. *tuple = Tuple(values, out_schema);
  54. return true;
  55. }
  56. }
  57. }
  58. return false;
  59. }

测试结果如下:

总结

通过这次实验,可以加深对目录、查询计划、迭代模型和 tuple 页布局的理解,算是收获满满的一次实验了,以上~~

CMU15445 (Fall 2019) 之 Project#3 - Query Execution 详解的更多相关文章

  1. CMU15445 (Fall 2019) 之 Project#1 - Buffer Pool 详解

    前言 这个实验有两个任务:时钟替换算法和缓冲池管理器,分别对应 ClockReplacer 和 BufferPoolManager 类,BufferPoolManager 会用 ClockReplac ...

  2. CMU15445 (Fall 2019) 之 Project#4 - Logging & Recovery 详解

    前言 这是 Fall 2019 的最后一个实验,要求我们实现预写式日志.系统恢复和存档点功能,这三个功能分别对应三个类 LogManager.LogRecovery 和 CheckpointManag ...

  3. CMU15445 (Fall 2019) 之 Project#2 - Hash Table 详解

    前言 该实验要求实现一个基于线性探测法的哈希表,但是与直接放在内存中的哈希表不同的是,该实验假设哈希表非常大,无法整个放入内存中,因此需要将哈希表进行分割,将多个键值对放在一个 Page 中,然后搭配 ...

  4. 分享知识-快乐自己:Hibernate 中Criteria Query查询详解

    1):Hibernate 中Criteria Query查询详解 当查询数据时,人们往往需要设置查询条件.在SQL或HQL语句中,查询条件常常放在where子句中. 此外,Hibernate还支持Cr ...

  5. iOS学习——iOS项目Project 和 Targets配置详解

    最近开始学习完整iOS项目的开发流程和思路,在实际的项目开发过程中,我们通常需要对项目代码和资料进行版本控制和管理,一般比较常用的SVN或者Github进行代码版本控制和项目管理.我们iOS项目的开发 ...

  6. CMU15445 之 Project#0 - C++ Primer 详解

    前言 这个实验主要用来测试大家对现代 C++ 的掌握程度,实验要求如下: 简单翻译一下上述要求,就是我们需要实现定义在 src/include/primer/p0_starter.h 中的三个类 Ma ...

  7. [Project] SpellCorrect源码详解

    该Project原来的应用场景是对电商网站中输入一个错误的商品名称进行智能纠错,比如iphoae纠错为iphone.以下介绍的这个版本对其作了简化,项目源代码地址参见我的github:https:// ...

  8. Project Server 2010 配置详解

    应公司要求,需要加强对项目的管理.安排我学习一下微软的Project是如何进行项目管理的,并且在公司服务器上搭建出这样的一个项目管理工具.可以通过浏览器就可以访问.我因为用的单机是Project Pr ...

  9. Android:联系人Contacts之ContentResolver query 参数详解

    注:本片整理自 http://blog.csdn.net/wssiqi/article/details/8132603 1.获取联系人姓名 一个简单的例子,这个函数获取设备上所有的联系人ID和联系人N ...

随机推荐

  1. .NET桌面程序应用WebView2组件集成网页开发3 WebView2的进程模型

    系列目录     [已更新最新开发文章,点击查看详细] WebView2 运行时使用与 Microsoft Edge 浏览器相同的进程模型. WebView2 运行时中的进程 WebView2 进程组 ...

  2. XCTF练习题---MISC---再见李华

    XCTF练习题---MISC---再见李华 flag:Stay hungry, Stay foolish. 解题步骤: 1.观察题目,下载附件 2.拿到手以后发现是一张图片,其中有MD5,破解一下没有 ...

  3. [AcWing 823] 排列

    点击查看代码 #include<iostream> using namespace std; const int N = 10; int n; void dfs(int u, int nu ...

  4. WIN10 使用注册表设置单应用KIOSK模式(不限win10版本)

    注意事项 下载安装Autologon.exe. 以下示例采用账号:- 账户:'KIOSK'- 密码:'KIOSK' 设置步骤 新建用户 1.进入windows设置->账户->其他用户,点击 ...

  5. 二叉查找树速通攻略 图文代码精心编写(Java实现)

    说在前面 如题目所言 这篇文章为了给下一篇二叉查找数做铺垫和前期知识准备,以便大家有良好的阅读体验,本来想合在一起的,但觉得有些长,所以就拆开了哈哈哈,还是新手向,两篇文章有些长,但如果能认真看下去, ...

  6. Android 图像显示系统 - 导出图层数据的方法介绍(dump GraphicBuffer raw data)

    一.前言 在项目的开发中,为了定位Android显示异常的原因:GPU渲染 or GPU合成 or HWC合成送显异常的问题.我们通常会把图层的原始数据写到文件,然后通过RGB或YUV的软件工具来查看 ...

  7. 一次IOS通知推送问题排查全过程

    原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 发现问题 在上周一个将要下班的夜晚,测试突然和我打招呼,说IOS推送的修复更新上线后存在问题,后台报错. 连忙跑到测试那 ...

  8. elasticSearch 7.6.1 入门及elasticSearch整合springboot

    一.ElasticSearch概述 官网:https://www.elastic.co/cn/downloads/elasticsearch Elaticsearch,简称为es,es是一个开源的高扩 ...

  9. 基本命令学习 -(3)Linux压缩和解压缩命令汇总

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 前言 Linux下的压缩和解压缩工具比较多,有时经常记不住,这里给大家汇总一下,方便大家查阅. ...

  10. 移动端input的disabled属性对字体颜色影响

    对于表单输入,input是很好的选择,这次记录主要是正对input的value值字体在Android和iOS(11)设备下显示不同问题: 如下图:1.2的区别主要是分别设置disabled.reado ...