【CMU15445】Fall 2019, Project 3: Query Execution 实验记录

2023-11-05 20:36

本文主要是介绍【CMU15445】Fall 2019, Project 3: Query Execution 实验记录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

    • 实验准备
    • 实验测试
    • Task 1: CREATING A CATALOG TABLE
      • 1.1 Create Table
      • 1.2 Get Table
    • Task 2: EXECUTORS
      • 2.1 什么是 executor
      • 2.2 什么是 Tuple
      • 2.3 实现 SeqScanExecutor
      • 2.4 实现 InsertExecutor
      • 2.5 实现 HashJoinExecutor
      • 2.6 实现 AggregationExecutor
    • Task 3: LINEAR PROBE HASH TABLE RETURNS
      • 3.1 实现 TmpTuple 和 TmpTuplePage
      • 3.2 实现 HashJoinExecutor
    • 实验运行结果

SQL 执行是由数据库解析器转化为一个由多个 executor 组成的 Query Plan 来完成的,本实验选择了火山模型来完成 query execution,这一次的 project 就是实现各种 exeutor,从而可以通过组装这些 executor 来完成数据库的查询、插入等操作。

火山模型如下图所示:

DB 的火山模型
SQL 被转换为一系列 executor 的执行,首先会对每个 executor 调用 Init() 方法来进行初始化,之后上层的 executor 通过调用下层的 executor 的 Next() 方法来获取一个 tuple,每一个 executor 都是如此,就类似一个火山一样,数据从底部逐渐向上喷发。

实验准备

  • 官方说明:https://15445.courses.cs.cmu.edu/fall2019/project3/

实验测试

Task 1:

cd build
make catalog_test
./test/catalog_test

Task 2~3:

cd build
make executor_test
./test/executor_test

Task 1: CREATING A CATALOG TABLE

这是一个热身任务,较为简单。

DB 内部通过维护 catalog 来维护数据库的元数据(比如 table 的创建与获取等),这个 task 需要实现 SimpleCatalogCreateTable()GetTable() 方法。

数据库的 table 主要包含 name 和 oid 两个属性,catalog 通过 table_ 属性维护 table 的 name 与 oid 的映射关系,其 GetTable() 方法需要同时支持对这两个的查询:

  • GetTable(const std::string &table_name):通过 name 来获取 table
  • GetTable(table_oid_t table_oid):通过 oid 来获取 table

一个 table 的元数据如下:

using table_oid_t = uint32_t;
using column_oid_t = uint32_t;struct TableMetadata {TableMetadata(Schema schema, std::string name, std::unique_ptr<TableHeap> &&table, table_oid_t oid): schema_(std::move(schema)), name_(std::move(name)), table_(std::move(table)), oid_(oid) {}Schema schema_;std::string name_;std::unique_ptr<TableHeap> table_;table_oid_t oid_;
};

Catalog 的关键属性字段有如下:

  • tables_:记录 table oid -> table metadata 的映射
  • names_:记录 table name -> table oid 的映射
  • next_table_oid:记录创建下一个 table 时可以分配的 oid

1.1 Create Table

catalog 创建 table 的实现:

  /*** Create a new table and return its metadata.* @param txn the transaction in which the table is being created* @param table_name the name of the new table* @param schema the schema of the new table* @return a pointer to the metadata of the new table*/auto CreateTable(Transaction *txn, const std::string &table_name, const Schema &schema) -> TableMetadata * {BUSTUB_ASSERT(names_.count(table_name) == 0, "Table names should be unique!");table_oid_t table_oid = next_table_oid_++;   // 这行代码很妙,在自增了 atomic 变量的同时将旧值返回给了 oidauto *tableHeap = new TableHeap(bpm_, lock_manager_, log_manager_, txn);auto *tableMetadata = new TableMetadata(schema, table_name, std::unique_ptr<TableHeap>(tableHeap), table_oid);tables_.insert({table_oid, std::unique_ptr<TableMetadata>(tableMetadata)});names_.insert({table_name, table_oid});return tableMetadata;}

1.2 Get Table

catalog 查询 table 的实现:

  /** @return table metadata by name */auto GetTable(const std::string &table_name) -> TableMetadata * {const table_oid_t table_oid = names_.at(table_name);const auto& table = tables_.at(table_oid);return table.get();}/** @return table metadata by oid */auto GetTable(table_oid_t table_oid) -> TableMetadata * {const auto &table = tables_.at(table_oid);return table.get();}

Task 2: EXECUTORS

2.1 什么是 executor

这个 task 就是要实现各个 executor:

  • seq_scan_executor:顺序扫描一个 table,每次 Next() 生成一个 tuple。
  • insert_executor:在一个 table 中插入 tuples。所要插入的 tuple 可能来源于指定的 tuple(称为 raw insert),也可能来源于调用 child executor 的 Next() 所得到的 tuple(称为 not-raw insert)。
  • hash_join_executor:使用 hash join 的算法将两个 table 进行 join,每次 Next() 生成一个 join 后的 tuple。
  • aggregation_executor:使用 aggregation 的相关计算,在本 task 中具体指的是带有 having 子句的 COUNT, SUM, MIN, MAX 这四个计算。

这些 executor 在每次生成一个 SQL 对应的 query plan 时被实例化,并在构造函数中传入上下文信息,在执行时保证在第一次调用 Next() 前会先被调用 Init() 来初始化。

构造函数中主要传入如下几个上下文信息:

  • ExecutorContext *exec_ctx:包含 buffer pool manager、transaction、catalog 等关于本次执行的全局上下文信息。
  • plan:一个 AbstractPlanNode 的子类的对象,它指示了这个 executor 本次所需要执行的任务的相关信息,比如这个 executor 需要输出 tuple 的 schema、用于过滤的判断谓词 predicate、aggregation 计算的具体类型(比如是 SUM 还是 MIN)等等。每种 executor 传入的 plan 也不同。
  • child executor:一个 executor 可能需要调用 child executor 的 Next() 方法来获得 tuple,从而完成自己的 Next()。比如 insert executor 需要从 child executor 来获取需要插入的 tuple,hash join executor 需要从 left child executor 和 right child executor 来获取需要进行 join 的左右两张表的 tuple。

executor 每次调用 Next() 后需要将相关状态记录到 executor 对象的属性中,这样下次 Next() 知道要继续做什么。

2.2 什么是 Tuple

一个 Tuple 类的实例就是一个 tuple,它包含两个关键属性:

  • char *data_:字节数组,表示这个 tuple 所拥有的数据
  • uint32_t size_:表示 data_ 的长度

它有一个关键的构造函数:

  // constructor for creating a new tuple based on input valueTuple(std::vector<Value> values, const Schema *schema);

values 是这个 tuple 的各个 column 的值,schema 表示如何解读这个 values,构造函数会将其序列化为字节数组。

2.3 实现 SeqScanExecutor

该 executor 顺序扫描一个 table,将满足 predicate 条件的所有 tuple 返回出去。

内部维护一个用于遍历 table 的 iterator,每次调用 Next() 时便会将 iterator 指向第一个满足 predicate 的 tuple 返回回去。

SeqScanExecutor::SeqScanExecutor(ExecutorContext *exec_ctx, const SeqScanPlanNode *plan): AbstractExecutor(exec_ctx),plan_(plan),table_metadata_(exec_ctx->GetCatalog()->GetTable(plan->GetTableOid())),table_iterator_(table_metadata_->table_->Begin(exec_ctx->GetTransaction())) {}void SeqScanExecutor::Init() {}auto SeqScanExecutor::Next(Tuple *tuple) -> bool {const auto *const predicate = plan_->GetPredicate();while (table_iterator_ != table_metadata_->table_->End()) {*tuple = *table_iterator_;++table_iterator_;if (predicate == nullptr || predicate->Evaluate(tuple, &table_metadata_->schema_).GetAs<bool>()) {return true;}}return false;
}

2.4 实现 InsertExecutor

用于向 table 中插入 tuples,调用 Next() 时插入所有的 tuples。根据所要插入的 tuple 的来源不同,分为两种情况:

  • raw insert:插入 plan_ 中指定的 tuple
  • not-raw insert:所要插入的 tuple 通过调用 child executor 的 Next() 来获得
InsertExecutor::InsertExecutor(ExecutorContext *exec_ctx, const InsertPlanNode *plan,std::unique_ptr<AbstractExecutor> &&child_executor): AbstractExecutor(exec_ctx),plan_(plan),child_executor_(std::move(child_executor)),table_metadata_(exec_ctx->GetCatalog()->GetTable(plan->TableOid())) {}auto InsertExecutor::GetOutputSchema() -> const Schema * { return plan_->OutputSchema(); }void InsertExecutor::Init() {}auto InsertExecutor::Next([[maybe_unused]] Tuple *tuple) -> bool {return plan_->IsRawInsert()? RawInsert(): NotRawInsert();
}auto InsertExecutor::RawInsert() -> bool {RID rid;const auto& rows = plan_->RawValues();for (const auto& rowValues : rows) {Tuple tuple(rowValues, &table_metadata_->schema_);if (!table_metadata_->table_->InsertTuple(tuple, &rid, exec_ctx_->GetTransaction())) {return false;}}return true;
}auto InsertExecutor::NotRawInsert() -> bool {Tuple tuple;RID rid;child_executor_->Init();while (child_executor_->Next(&tuple)) {if (!table_metadata_->table_->InsertTuple(tuple, &rid, exec_ctx_->GetTransaction())) {return false;}}return true;
}

2.5 实现 HashJoinExecutor

这个 executor 使用 hash join 算法将两个 table 进行 join,这个算法可以分为两步:

  1. build 阶段:顺序扫描 left table 每一个 tuple,将每个 tuple 中需要 join 的 column value 计算 hash 放入 hash table 中;
  2. probe 阶段:顺序扫描 right table 每一个 tuple,对每个 tuple 中需要 join 的 column value 计算 hash,从 hash table 中查出所有哈希碰撞的 left tuples,然后逐一检查每个 left tuple 能否与 right tuple 进行 join,如果可以,则进行 join 并生成一个 tuple 返回。

在这个 task 中,hash table 使用了一个构建于内存中的 SimpleHashTable,build 阶段在 Init() 中完成,每次 Next() 返回一个 probe 阶段生成的 tuple。

HashJoinExecutor::HashJoinExecutor(ExecutorContext *exec_ctx, const HashJoinPlanNode *plan,std::unique_ptr<AbstractExecutor> &&left, std::unique_ptr<AbstractExecutor> &&right): AbstractExecutor(exec_ctx),plan_(plan),jht_("simple-ht", exec_ctx->GetBufferPoolManager(), jht_comp_, jht_num_buckets_, jht_hash_fn_),left_(std::move(left)),right_(std::move(right)),collisions_(nullptr),collision_index_(0) {}void HashJoinExecutor::Init() {left_->Init();right_->Init();// HashJoin build 阶段Tuple tuple;auto * const txn = exec_ctx_->GetTransaction();while (left_->Next(&tuple)) {const auto hash_value = HashValues(&tuple, left_->GetOutputSchema(), plan_->GetLeftKeys());jht_.Insert(txn, hash_value, tuple);}
}auto HashJoinExecutor::Next(Tuple *tuple) -> bool {// HashJoin probe 的阶段while (NextCursor()) {// 初始化相关数据const auto *const predicate = plan_->Predicate();  // 用于判断是否能够 join 的 predicateauto *left_tuple = &collisions_->at(collision_index_);  // left tupleauto *right_tuple = &right_tuple_;  // right tupleconst auto *const output_schema = this->GetOutputSchema();  // join 之后的 output schemaconst auto col_count = output_schema->GetColumnCount();  // output 的 column 个数// 判断 left-tuple 和 right-tuple 是否能够 joinbool jointable = true;if (predicate != nullptr) {jointable = predicate->EvaluateJoin(left_tuple, left_->GetOutputSchema(), right_tuple, right_->GetOutputSchema()).GetAs<bool>();}if (jointable) {// 将两个 tuple 进行 joinstd::vector<Value> values;for (size_t col_index = 0; col_index < col_count; col_index++) {const auto& expr = output_schema->GetColumn(col_index).GetExpr();const auto& value = expr->EvaluateJoin(left_tuple, left_->GetOutputSchema(), right_tuple, right_->GetOutputSchema());values.push_back(value);}*tuple = Tuple(values, output_schema);return true;}}return false;
}/** 指向下一个待匹配的 left tuple 和 right tuple */
auto HashJoinExecutor::NextCursor() -> bool {collision_index_++;while (collisions_ == nullptr || collision_index_ >= collisions_->size()) {bool has_more = right_->Next(&right_tuple_);if (!has_more) {return false;}auto r_hash_value = HashValues(&right_tuple_, right_->GetOutputSchema(), plan_->GetRightKeys());collisions_ = std::make_unique<std::vector<Tuple>>();jht_.GetValue(exec_ctx_->GetTransaction(), r_hash_value, collisions_.get());collision_index_ = 0;}return true;
}

2.6 实现 AggregationExecutor

这个 executor 实现带有 having 条件的 COUNT, SUM, MIN, MAX 这四种聚合计算。

实现 aggregation 也分成两步:

  1. Partition 阶段:这一阶段完成 group by 操作,将所有 tuples 根据 GroupBy 的 keys 分成一个个 partition;
  2. ReHash 阶段:将每一个 partition 内的 tuples 根据聚合计算的类型计算出一个 aggregation value。

本 project 已经帮我们实现了一个 SimpleAggregationHashTable,它有一个已实现好的 InsertCombine() 方法,我们只需要遍历所有 tuples,然后分别调用 InsertCombine,就会帮我们完成分区和每个分区的 aggregation value 的计算。

所以我们可以在 executor 的 Init() 阶段遍历所有 tuples,并调用 InsertCombine() 计算出所有分区的 aggregation value,在 Next() 遍历每一个分区的聚合结果,判断一下是否满足 having 条件,将每个满足条件的分区构造为一个 tuple 返回出去。

AggregationExecutor::AggregationExecutor(ExecutorContext *exec_ctx, const AggregationPlanNode *plan,std::unique_ptr<AbstractExecutor> &&child): AbstractExecutor(exec_ctx),plan_(plan),child_(std::move(child)),aht_(plan_->GetAggregates(), plan_->GetAggregateTypes()),aht_iterator_(aht_.End()) {}auto AggregationExecutor::GetChildExecutor() const -> const AbstractExecutor * { return child_.get(); }auto AggregationExecutor::GetOutputSchema() -> const Schema * { return plan_->OutputSchema(); }void AggregationExecutor::Init() {child_->Init();// 构建 hash tableTuple tuple;while (child_->Next(&tuple)) {const auto agg_key = MakeKey(&tuple);const auto agg_value = MakeVal(&tuple);aht_.InsertCombine(agg_key, agg_value);  // 相当于 ReHash 阶段的存储 (GroupKey -> RunningVal)}aht_iterator_ = aht_.Begin();
}auto AggregationExecutor::Next(Tuple *tuple) -> bool {const auto *const having_predicate = plan_->GetHaving();while (aht_iterator_ != aht_.End()) {const auto &agg_key = aht_iterator_.Key();const auto &agg_value = aht_iterator_.Val();++aht_iterator_;// 判断是否满足 having 条件bool isSatified = true;if (having_predicate != nullptr) {isSatified = having_predicate->EvaluateAggregate(agg_key.group_bys_, agg_value.aggregates_).GetAs<bool>();}// 如果满足 having 条件,则将 agg_key 和 agg_value 构建为 output_schema 的形式,并返回if (isSatified) {std::vector<Value> values;const auto *const output_schema = GetOutputSchema();for (size_t i = 0; i < output_schema->GetColumnCount(); i++) {const auto * const expr = output_schema->GetColumn(i).GetExpr();values.push_back(expr->EvaluateAggregate(agg_key.group_bys_, agg_value.aggregates_));}*tuple = Tuple(values, GetOutputSchema());return true;}}return false;
}

Task 3: LINEAR PROBE HASH TABLE RETURNS

之前我们实现 hash join executor 时,使用的 hash table 是一个构建于内存的 SimpleHashTable,当内存无法存放一个 left table 的所有 tuples 时,这种 hash table 便会无法完成运算。本 task 目标就是实现将 hash join executor 中的 SimpleHashTable 更换为我们在 project-2 中实现的构建于磁盘的 LinearProbeHashTable

3.1 实现 TmpTuple 和 TmpTuplePage

由于 Tuple 类中的 data_ 是一个字节数组,存储有这个 tuple 的实际数据,占用空间较大,将它作为 hash table 的 value 时会惹上很多麻烦,所有本 project 提出可以先实现一个 TmpTuple 和 TmpTuplePage:

  • TmpTuplePage:它是 Page 类的子类,由 buffer pool manager 管理,这种 page 专门用于存储 Tuple 对象,其中包含这个 tuple 的实际数据。
  • TmpTuple:每个它的实例对应一个真正的 tuple,但它不存储 tuple 的实际数据,而是存储了这个 tuple 在哪个 TmpTuplePage 和它在这个 page 上的 offset,这样在构建 hash table 时,可以使用 TmpTuple 对象来作为 value。

本 project 帮我们设计了 TmpTuplePage 的空间布局:

/*** TmpTuplePage format:** Sizes are in bytes.* | PageId (4) | LSN (4) | FreeSpace (4) | (free space) | TupleSize2 (4) | TupleData2 | TupleSize1 (4) | TupleData1 |** We choose this format because DeserializeExpression expects to read Size followed by Data.*/
  • 最开始是 4 个 byte 来存储 page id
  • 之后 4 个 byte 存储 LSN
  • 之后 4 个 byte 存储 FreeSpacePointer,它是一个指向可用空间的相对位移,它的初始值就是 page 的末尾
  • 剩下的所有空间用于存储 tuples,自末尾向前存储,即先在末尾存一个 tuple 1,再向前移动 FreeSpacePointer 存储下一个 tuple 2

TmpTuple 如下:

class TmpTuple {public:TmpTuple() = default;TmpTuple(page_id_t page_id, size_t offset) : page_id_(page_id), offset_(offset) {}inline bool operator==(const TmpTuple &rhs) const { return page_id_ == rhs.page_id_ && offset_ == rhs.offset_; }page_id_t GetPageId() const { return page_id_; }size_t GetOffset() const { return offset_; }void SetPageId(page_id_t page_id) { page_id_ = page_id; }void SetOffset(size_t offset) { offset_ = offset; }private:page_id_t page_id_;size_t offset_;
};

TmpTuplePage 如下:

class TmpTuplePage : public Page {public:void Init(page_id_t page_id, uint32_t page_size) {// Set the page ID.memcpy(GetData(), &page_id, sizeof(page_id_t));// Set FreeSpace// 因为 free-space-pointer 一开始应该是指向 page 的最末尾处,所以 free-space-pointer 的值应当就是 PAGE_SIZESetFreeSpacePointer(page_size);}auto GetTablePageId() -> page_id_t { return *reinterpret_cast<page_id_t *>(GetData()); }auto Insert(const Tuple &tuple, TmpTuple *out) -> bool {const auto tuple_size = tuple.GetLength();// determine whether there is enough space to insert tupleif (GetFreeSpaceRemaining() < tuple_size + SIZE_TUPLE_SIZE) {return false;}// insert tuple and its sizeSetFreeSpacePointer(GetFreeSpacePointer() - tuple_size);memcpy(GetData() + GetFreeSpacePointer(), tuple.GetData(), tuple_size);SetFreeSpacePointer(GetFreeSpacePointer() - SIZE_TUPLE_SIZE);memcpy(GetData() + GetFreeSpacePointer(), &tuple_size, SIZE_TUPLE_SIZE);out->SetPageId(GetPageId());out->SetOffset(GetFreeSpacePointer());return true;}auto Get(uint32_t offset) -> char * {return GetData() + offset;}private:static_assert(sizeof(page_id_t) == 4);static constexpr size_t SIZE_TABLE_PAGE_HEADER = 12;static constexpr size_t SIZE_TUPLE_SIZE = 4;  // TupleSize 字段的 sizestatic constexpr size_t OFFSET_FREE_SPACE = 8;void SetFreeSpacePointer(uint32_t free_space_pointer) {memcpy(GetData() + OFFSET_FREE_SPACE, &free_space_pointer, sizeof(free_space_pointer));}auto GetFreeSpacePointer() -> uint32_t { return *reinterpret_cast<uint32_t *>(GetData() + OFFSET_FREE_SPACE); }auto GetFreeSpaceRemaining() -> uint32_t { return GetFreeSpacePointer() - SIZE_TABLE_PAGE_HEADER; }
};

3.2 实现 HashJoinExecutor

完成了 TmpTuplePageTmpTuple,我们就可以利用它们和 LinearProbeHashTable 共同实现 hash join executor。

我们将 tuple 在 key columns 上的 values 计算的哈希值作为 key,将这个 tuple 对应的 tmp_tuple 作为 value,来构建 hash table。

由于 LinearProbeHashTable 是一个 C++ template,因此我们需要先在 hash_table_block_page.cpp 和 linear_probe_hash_table.cpp 文件中添加如下代码做模板特例化

// 在 hash_table_block_page.cpp 末尾
template class HashTableBlockPage<hash_t, TmpTuple, HashComparator>;// 在 linear_probe_hash_table.cpp 末尾
template class LinearProbeHashTable<hash_t, TmpTuple, HashComparator>;

在 hash_join_executor.cpp 加上:

// 在 hash_join_executor.cpp 中
template class LinearProbeHashTable<hash_t, TmpTuple, HashComparator>;
template class HashTableBlockPage<hash_t, TmpTuple, HashComparator>;

在 HashJoinExecutor 中声明的 hash table 的类型是 HT,之前 using HT = SimpleHashTable,现在需要将其更换为:

using HashJoinKeyType = hash_t;
using HashJoinValType = TmpTuple;
using HT = LinearProbeHashTable<HashJoinKeyType, HashJoinValType, HashComparator>;

最后是 HashJoinExecutor 的实现代码:

template class LinearProbeHashTable<hash_t, TmpTuple, HashComparator>;
template class HashTableBlockPage<hash_t, TmpTuple, HashComparator>;HashJoinExecutor::HashJoinExecutor(ExecutorContext *exec_ctx, const HashJoinPlanNode *plan,std::unique_ptr<AbstractExecutor> &&left, std::unique_ptr<AbstractExecutor> &&right): AbstractExecutor(exec_ctx),plan_(plan),jht_("linear-probe-ht", exec_ctx->GetBufferPoolManager(), jht_comp_, jht_num_buckets_, jht_hash_fn_),left_(std::move(left)),right_(std::move(right)),collisions_(nullptr),collision_index_(0) {}void HashJoinExecutor::Init() {left_->Init();right_->Init();// HashJoin build 阶段Tuple tuple;auto *const txn = exec_ctx_->GetTransaction();auto *const bpm = exec_ctx_->GetBufferPoolManager();page_id_t tmp_page_id;TmpTuple tmp_tuple;auto *tmp_tuple_page = reinterpret_cast<TmpTuplePage *>(bpm->NewPage(&tmp_page_id));tmp_tuple_page->Init(tmp_page_id, PAGE_SIZE);while (left_->Next(&tuple)) {const auto hash_value = HashValues(&tuple, left_->GetOutputSchema(), plan_->GetLeftKeys());// 如果 tmp_page 满了的话,新建一个 page 再 insertif (!tmp_tuple_page->Insert(tuple, &tmp_tuple)) {bpm->UnpinPage(tmp_page_id, true);tmp_tuple_page = reinterpret_cast<TmpTuplePage *>(bpm->NewPage(&tmp_page_id));tmp_tuple_page->Init(tmp_page_id, PAGE_SIZE);tmp_tuple_page->Insert(tuple, &tmp_tuple);}// 将 tmp_tuple 插入到 hash table 中// - tmp_tuple 中存的是实际 tuple 的位置// - 实际的 tuple 放在了 tmp_tuple_page 中jht_.Insert(txn, hash_value, tmp_tuple);}bpm->UnpinPage(tmp_page_id, true);
}auto HashJoinExecutor::Next(Tuple *tuple) -> bool {auto* const bpm = exec_ctx_->GetBufferPoolManager();// HashJoin probe 的阶段while (NextCursor()) {// 初始化相关数据const auto *const predicate = plan_->Predicate();  // 用于判断是否能够 join 的 predicate// left tupleconst auto *const left_tmp_tuple = &collisions_->at(collision_index_);auto *const left_tuple_page = reinterpret_cast<TmpTuplePage *>(bpm->FetchPage(left_tmp_tuple->GetPageId()));Tuple left_tuple;left_tuple.DeserializeFrom(left_tuple_page->Get(left_tmp_tuple->GetOffset()));bpm->UnpinPage(left_tmp_tuple->GetPageId(), false);const auto *const right_tuple = &right_tuple_;  // right tupleconst auto *const output_schema = this->GetOutputSchema();  // join 之后的 output schemaconst auto col_count = output_schema->GetColumnCount();  // output 的 column 个数// 判断 left-tuple 和 right-tuple 是否能够 joinbool jointable = true;if (predicate != nullptr) {jointable = predicate->EvaluateJoin(&left_tuple, left_->GetOutputSchema(), right_tuple, right_->GetOutputSchema()).GetAs<bool>();}if (jointable) {// 将两个 tuple 进行 joinstd::vector<Value> values;for (size_t col_index = 0; col_index < col_count; col_index++) {const auto* expr = output_schema->GetColumn(col_index).GetExpr();const auto value = expr->EvaluateJoin(&left_tuple, left_->GetOutputSchema(), right_tuple, right_->GetOutputSchema());values.push_back(value);}*tuple = Tuple(values, output_schema);return true;}}return false;
}/** 指向下一个待匹配的 left tuple 和 right tuple */
auto HashJoinExecutor::NextCursor() -> bool {collision_index_++;while (collisions_ == nullptr || collision_index_ >= collisions_->size()) {const bool has_more = right_->Next(&right_tuple_);if (!has_more) {return false;}const auto r_hash_value = HashValues(&right_tuple_, right_->GetOutputSchema(), plan_->GetRightKeys());collisions_ = std::make_unique<std::vector<TmpTuple>>();jht_.GetValue(exec_ctx_->GetTransaction(), r_hash_value, collisions_.get());collision_index_ = 0;}return true;
}

实验运行结果

在这里插入图片描述

这篇关于【CMU15445】Fall 2019, Project 3: Query Execution 实验记录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/352337

相关文章

BUUCTF靶场[web][极客大挑战 2019]Http、[HCTF 2018]admin

目录   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 [web][HCTF 2018]admin 考点:弱密码字典爆破 四种方法:   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 访问环境 老规矩,我们先查看源代码

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件

记录每次更新到仓库 —— Git 学习笔记 10

记录每次更新到仓库 文章目录 文件的状态三个区域检查当前文件状态跟踪新文件取消跟踪(un-tracking)文件重新跟踪(re-tracking)文件暂存已修改文件忽略某些文件查看已暂存和未暂存的修改提交更新跳过暂存区删除文件移动文件参考资料 咱们接着很多天以前的 取得Git仓库 这篇文章继续说。 文件的状态 不管是通过哪种方法,现在我们已经有了一个仓库,并从这个仓

STM32(十一):ADC数模转换器实验

AD单通道: 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO,把GPIO配置成模拟输入的模式。 3.配置多路开关,把左面通道接入到右面规则组列表里。 4.配置ADC转换器, 包括AD转换器和AD数据寄存器。单次转换,连续转换;扫描、非扫描;有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。 5.ADC_CMD 开启ADC。 void RCC_AD

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

HNU-2023电路与电子学-实验3

写在前面: 一、实验目的 1.了解简易模型机的内部结构和工作原理。 2.分析模型机的功能,设计 8 重 3-1 多路复用器。 3.分析模型机的功能,设计 8 重 2-1 多路复用器。 4.分析模型机的工作原理,设计模型机控制信号产生逻辑。 二、实验内容 1.用 VERILOG 语言设计模型机的 8 重 3-1 多路复用器; 2.用 VERILOG 语言设计模型机的 8 重 2-1 多

perl的学习记录——仿真regression

1 记录的背景 之前只知道有这个强大语言的存在,但一直侥幸自己应该不会用到它,所以一直没有开始学习。然而人生这么长,怎就确定自己不会用到呢? 这次要搭建一个可以自动跑完所有case并且打印每个case的pass信息到指定的文件中。从而减轻手动跑仿真,手动查看log信息的重复无效低质量的操作。下面简单记录下自己的思路并贴出自己的代码,方便自己以后使用和修正。 2 思路整理 作为一个IC d

Debugging Lua Project created in Cocos Code IDE creates “Waiting for debugger to connect” in Win-7

转自 I Installed Cocos Code IDE and created a new Lua Project. When Debugging the Project(F11) the game window pops up and gives me the message waiting for debugger to connect and then freezes. Also a

SSM项目使用AOP技术进行日志记录

本步骤只记录完成切面所需的必要代码 本人开发中遇到的问题: 切面一直切不进去,最后发现需要在springMVC的核心配置文件中中开启注解驱动才可以,只在spring的核心配置文件中开启是不会在web项目中生效的。 之后按照下面的代码进行配置,然后前端在访问controller层中的路径时即可观察到日志已经被正常记录到数据库,代码中有部分注释,看不懂的可以参照注释。接下来进入正题 1、导入m

flume系列之:记录一次flume agent进程被异常oom kill -9的原因定位

flume系列之:记录一次flume agent进程被异常oom kill -9的原因定位 一、背景二、定位问题三、解决方法 一、背景 flume系列之:定位flume没有关闭某个时间点生成的tmp文件的原因,并制定解决方案在博主上面这篇文章的基础上,在机器内存、cpu资源、flume agent资源都足够的情况下,flume agent又出现了tmp文件无法关闭的情况 二、