CMU_15445_P3_BUSTUB_Query_Execution导读
BUSTUB中SQL语句的Plan Execution 导读
Project3 中主要讲述的是SQL语言的Query Executor过程, 实际步骤中包含Query Optimization的过程, 下图是一个 Query 在数据库中 Execute 的整体流程.
在 关系数据库 Query Execution 中, 我们已经介绍了关系数据库中 SQL 语句的组织形式以及执行的形式. 在本篇导读中, 我们将博客中的数据结构映射到 BUSTUB 的实现中, 看一下 BUSTUB 是如何实现.
SQL 语句解析
BUSTUB 有一个很好用的功能就是将需要执行的 SQL 语句进行解析, 显示在 BUSTUB 中执行该语句的所有 Plan_Node 的执行步骤, 并且还附带这些 Plan_Node 的表达式(expression) 信息.
下面是使用 EXPLAIN
的一个例子.
bustub> EXPLAIN SELECT * FROM __mock_table_1;
=== BINDER ===
BoundSelect {
table=BoundBaseTableRef { table=__mock_table_1, oid=0 },
columns=[__mock_table_1.colA, __mock_table_1.colB],
groupBy=[],
having=,
where=,
limit=,
offset=,
order_by=[],
is_distinct=false,
}
=== PLANNER ===
Projection { exprs=[#0.0, #0.1] } | (__mock_table_1.colA:INTEGER, __mock_table_1.colB:INTEGER)
MockScan { table=__mock_table_1 } | (__mock_table_1.colA:INTEGER, __mock_table_1.colB:INTEGER)
=== OPTIMIZER ===
MockScan { table=__mock_table_1 } | (__mock_table_1.colA:INTEGER, __mock_table_1.colB:INTEGER)
在 EXPLAIN
中, 执行步骤总结为了三部分, 分别是 BINDER
, PLANNER
, OPTIMIZER
. 这个执行步骤对应我们前面关系数据库 Query Execution中介绍的关系数据库的执行流程, 在 BUSTUB 中, BINDER 阶段已经完成了, 我们需要完成的事 PLANNER 部分和 OPTIMIZER 阶段.
BINDER
在上面 EXPLAIN
输出中的 BINDER 部分列出的重要信息为数据库表的信息, 列 columns 相关的信息, 这些信息是数据库存储直接相关的内容.
PLANNER
PLANNER 对应的是我们前面描述的未被优化过的 PLAN_NODE, 和前面类似, 有各种各样的 PLAN_NODE, PLAN_NODE 表示的是关系操作符或者非关系操作符, 例如上面的 Projection
就是关系操作符, 而 MockScan
是非关系操作符, 这个操作符是一个预制的操作符, 用来在BUSTUB预制的数据库表中遍历每一行的数据. PLANNER 还有一个非常重要的部分就是 expression, 也就是 exprs部分, 例如上述的例子中, Projection 的expression 就是 exprs=[#0.0, #0.1]
, 后面我们会解释, 这是一个 ColumnValue Expression
, 是一种重要的表达式类型.
OPTIMIZAER
在DBMS中, 在执行最后的 Plan 之间的最后一步是将 Logical_Plan 优化, 这个优化也就是 OPTIMIZAER, 实际在执行中的 OPTIMIZER 根据子节点与父节点的关系, 或者相关性有很多种, 后续我们也需要自己实现一些. 而在 EXPLAIN
的输出中, OPTIMIZER 列出的是优化后的 PLAN 的信息.
BUSTUB 中的表达式
在 BUSTUB 中, 所有表达式都是继承自 abstract_expression.h
类. 因此我们需要解释一下这个类:
class AbstractExpression;
// AbstractExpressionRef 就是指向AbstractExpression类型的共享指针
using AbstractExpressionRef = std::shared_ptr<AbstractExpression>;
/**
* AbstractExpression is the base class of all the expressions in the system.
* Expressions are modeled as trees, i.e. every expression may have a variable number of children.
*/
class AbstractExpression {
public:
/**
* 表达式的类型是一颗树, 它的孩子节点是指向AbstractExpression类型的共享指针
* Create a new AbstractExpression with the given children and return type.
* @param children the children of this abstract expression
* @param ret_type the return type of this abstract expression when it is evaluated
*/
AbstractExpression(std::vector<AbstractExpressionRef> children, TypeId ret_type)
: children_{std::move(children)}, ret_type_{ret_type} {}
/** Virtual destructor. */
virtual ~AbstractExpression() = default;
/** @return The value obtained by evaluating the tuple with the given schema
* 不同的表达式会根据不同的操作, 或者不同的方式重载这个函数, 表示执行不同的步骤
* 这里的 tuple 和 schema 是表达式中的操作数, column_value_expression 中会使用到
*/
virtual auto Evaluate(const Tuple *tuple, const Schema &schema) const -> Value = 0;
/**
* Returns the value obtained by evaluating a JOIN.
* @param left_tuple The left tuple
* @param left_schema The left tuple's schema
* @param right_tuple The right tuple
* @param right_schema The right tuple's schema
* @return The value obtained by evaluating a JOIN on the left and right
* 不同的表达式也会重载这个函数, 返回一个表达式的结果, 表示对两个 Tuples 的 JOIN 的结果, 例如 arithmetic_expression
* 会将两个 Tuples 的计算的结果相加. 然后返回
*/
virtual auto EvaluateJoin(const Tuple *left_tuple, const Schema &left_schema, const Tuple *right_tuple,
const Schema &right_schema) const -> Value = 0;
/** @return the child_idx'th child of this expression */
auto GetChildAt(uint32_t child_idx) const -> const AbstractExpressionRef & { return children_[child_idx]; }
/** @return the children of this expression, ordering may matter */
auto GetChildren() const -> const std::vector<AbstractExpressionRef> & { return children_; }
/** @return the type of this expression if it were to be evaluated */
virtual auto GetReturnType() const -> TypeId { return ret_type_; }
/** @return the string representation of the plan node and its children */
virtual auto ToString() const -> std::string { return "<unknown>"; }
/** @return a new expression with new children
* 用于克隆节点, 用于返回一个新的节点, 作为优化节点
*/
virtual auto CloneWithChildren(std::vector<AbstractExpressionRef> children) const
-> std::unique_ptr<AbstractExpression> = 0;
/** The children of this expression. Note that the order of appearance of children may matter. */
std::vector<AbstractExpressionRef> children_;
private:
/** The return type of this expression. */
TypeId ret_type_;
};
不同的表达式有不同得作用, 并且会作用于不同得 ExecutorPlanNode 中, 在 BUSTUB 中, 有下列几种表达式:
- abstract_expression
- arithmetic_expression
- column_value_expression
- comparison_expression
- constant_value_expression
- logic_expression
- string_expression
我们分别介绍一下这些表达式:
抽象表达式在也就是我们刚才介绍的基类, 就不再过多介绍了
arithmetic_expression
数学表达式用于计算, 当前的 arithmetic_expression
仅支持整数类型的计算, 并且仅支持整数的加减计算, 我们可以看到在 arithmetic_expression
中重载了 Evaluate
和 EvaluateJoin
函数, 并且新增了计算的部分:
// 递归的计算表达式的左边与右边的结算结果, 然后计算左子树与右子树的变量的计算结果
auto Evaluate(const Tuple *tuple, const Schema &schema) const -> Value override {
Value lhs = GetChildAt(0)->Evaluate(tuple, schema);
Value rhs = GetChildAt(1)->Evaluate(tuple, schema);
auto res = PerformComputation(lhs, rhs);
if (res == std::nullopt) {
return ValueFactory::GetNullValueByType(TypeId::INTEGER);
}
return ValueFactory::GetIntegerValue(*res);
}
auto EvaluateJoin(const Tuple *left_tuple, const Schema &left_schema, const Tuple *right_tuple,
const Schema &right_schema) const -> Value override {
Value lhs = GetChildAt(0)->EvaluateJoin(left_tuple, left_schema, right_tuple, right_schema);
Value rhs = GetChildAt(1)->EvaluateJoin(left_tuple, left_schema, right_tuple, right_schema);
auto res = PerformComputation(lhs, rhs);
if (res == std::nullopt) {
return ValueFactory::GetNullValueByType(TypeId::INTEGER);
}
// current arithmetic_expression only support Integer Calculating
return ValueFactory::GetIntegerValue(*res);
}
ArithmeticType compute_type_;
private:
auto PerformComputation(const Value &lhs, const Value &rhs) const -> std::optional<int32_t> {
if (lhs.IsNull() || rhs.IsNull()) {
return std::nullopt;
}
switch (compute_type_) {
case ArithmeticType::Plus:
return lhs.GetAs<int32_t>() + rhs.GetAs<int32_t>();
case ArithmeticType::Minus:
return lhs.GetAs<int32_t>() - rhs.GetAs<int32_t>();
default:
UNREACHABLE("Unsupported arithmetic type.");
}
}
column_value_expression
column_value_expression 表达式用于获取一列的值, 这个表达式在很多 Plan_Node 中都会用到, 表达式在 EXPLAIN
中的格式为: { exprs=[#0.0, #0.1] }
, 例如 Projection
Plan_Node 中.
auto Evaluate(const Tuple *tuple, const Schema &schema) const -> Value override {
return tuple->GetValue(&schema, col_idx_);
}
auto EvaluateJoin(const Tuple *left_tuple, const Schema &left_schema, const Tuple *right_tuple,
const Schema &right_schema) const -> Value override {
return tuple_idx_ == 0 ? left_tuple->GetValue(&left_schema, col_idx_)
: right_tuple->GetValue(&right_schema, col_idx_);
}
auto GetTupleIdx() const -> uint32_t { return tuple_idx_; }
auto GetColIdx() const -> uint32_t { return col_idx_; }
private:
/** Tuple index 0 = left side of join, tuple index 1 = right side of join */
uint32_t tuple_idx_;
/** Column index refers to the index within the schema of the tuple, e.g. schema {A,B,C} has indexes {0,1,2} */
uint32_t col_idx_;
ComparisonExpression
ComparisonExpression 表达式与arithmetic_expression表达式类似, 是用于数学计算的表达式, ComparisonExpression
主要用于比较, 例如
SELECT * FROM t1 WHERE v1 = 1;
SELECT * FROM t1 WHERE v1 < 10;
ComparisonExpression 是最常见的 FilterPlanNode
表达式类型.
子表达式包括:
左操作数: ColumnValueExpression.
右操作数: ConstantValueExpression 或另一个 ColumnValueExpression.
在实现的时候, 我们可以看到递归的调用 Evaluate
和 EvaluateJoin
函数.
auto Evaluate(const Tuple *tuple, const Schema &schema) const -> Value override {
Value lhs = GetChildAt(0)->Evaluate(tuple, schema);
Value rhs = GetChildAt(1)->Evaluate(tuple, schema);
return ValueFactory::GetBooleanValue(PerformComparison(lhs, rhs));
}
auto EvaluateJoin(const Tuple *left_tuple, const Schema &left_schema, const Tuple *right_tuple,
const Schema &right_schema) const -> Value override {
Value lhs = GetChildAt(0)->EvaluateJoin(left_tuple, left_schema, right_tuple, right_schema);
Value rhs = GetChildAt(1)->EvaluateJoin(left_tuple, left_schema, right_tuple, right_schema);
return ValueFactory::GetBooleanValue(PerformComparison(lhs, rhs));
}
ConstantValueExpression
ConstantValueExpression 是常数类型的表达式, 这种表达式比较简单, 返回的是常数, 在很多 Plan_Node 中都会使用到, 例如 Insert 语句中, 从 SQL 语句中读取的数字就是常数, 最后会将这个常数写到数据库表中.
因此 ConstantValueExpression
的实现也很简单:
auto Evaluate(const Tuple *tuple, const Schema &schema) const -> Value override { return val_; }
auto EvaluateJoin(const Tuple *left_tuple, const Schema &left_schema, const Tuple *right_tuple,
const Schema &right_schema) const -> Value override {
return val_;
LogicExpression
条件类型:
逻辑操作: SQL语句中的 AND, OR 等逻辑操作符.
例如下面的例子中, 在条件中的 AND
与 OR
逻辑操作符.
SELECT * FROM t1 WHERE v1 = 1 AND v2 > 5;
SELECT * FROM t1 WHERE v1 = 1 OR v2 < 3;
表达式结构:
LogicExpression 表示逻辑组合, 左右子表达式可以是: ComparisonExpression, 与其他 LogicExpression(组成嵌套结构).
例如:
LogicExpression (AND)
├── ComparisonExpression (v1 = 1)
└── ComparisonExpression (v2 > 5)
StringExpression
StringExpression 通常用于字符串相关操作, 如 LIKE、NOT LIKE、ILIKE. 例如语句:
SELECT * FROM t1 WHERE name LIKE 'A%';
表达式结构:
StringExpression 可能包含:
左子表达式: ClumnValueExpression(字符串列)
右子表达式: ConstantValueExpression(模式字符串)
在实现的是配合 Compute 函数一起:
// 变换字符串的大写与小写
auto Compute(const std::string &val) const -> std::string {
// TODO(student): implement upper / lower.
std::string result;
if (this->expr_type_ == bustub::StringExpressionType::Lower) {
for (auto c : val) {
result.push_back(std::tolower(c));
}
} else {
for (auto c : val) {
result.push_back(std::toupper(c));
}
}
return result;
}
auto Evaluate(const Tuple *tuple, const Schema &schema) const -> Value override {
Value val = GetChildAt(0)->Evaluate(tuple, schema);
auto str = val.GetAs<char *>();
return ValueFactory::GetVarcharValue(Compute(str));
}
auto EvaluateJoin(const Tuple *left_tuple, const Schema &left_schema, const Tuple *right_tuple,
const Schema &right_schema) const -> Value override {
Value val = GetChildAt(0)->EvaluateJoin(left_tuple, left_schema, right_tuple, right_schema);
auto str = val.GetAs<char *>();
return ValueFactory::GetVarcharValue(Compute(str));
}
还有值得一提的是, 每个表达式都从抽象表达式中继承重载了 ToString()
函数, 这个函数本身是用于在 EXPLAIN
中输出显示用的, 在实际中, 我们可以在 Plan_Node 的 Executor 中使用该函数, 查看 Expression, 可以显示的看到该表达式, 方便调试.
BUSTUB 中的 Plan_Node
BUSTUB 中的 Plan_Node 都延伸自下面的基类:
class AbstractPlanNode;
using AbstractPlanNodeRef = std::shared_ptr<const AbstractPlanNode>;
/**
* AbstractPlanNode represents all the possible types of plan nodes in our system.
* Plan nodes are modeled as trees, so each plan node can have a variable number of children.
* Per the Volcano model, the plan node receives the tuples of its children.
* The ordering of the children may matter.
*/
class AbstractPlanNode {
public:
/**
* Create a new AbstractPlanNode with the specified output schema and children.
* @param output_schema The schema for the output of this plan node
* @param children The children of this plan node
*/
AbstractPlanNode(SchemaRef output_schema, std::vector<AbstractPlanNodeRef> children)
: output_schema_(std::move(output_schema)), children_(std::move(children)) {}
/** Virtual destructor. */
virtual ~AbstractPlanNode() = default;
/** @return the schema for the output of this plan node */
auto OutputSchema() const -> const Schema & { return *output_schema_; }
/** @return the child of this plan node at index child_idx */
auto GetChildAt(uint32_t child_idx) const -> AbstractPlanNodeRef { return children_[child_idx]; }
/** @return the children of this plan node */
auto GetChildren() const -> const std::vector<AbstractPlanNodeRef> & { return children_; }
/** @return the type of this plan node */
virtual auto GetType() const -> PlanType = 0;
/** @return the string representation of the plan node and its children */
auto ToString(bool with_schema = true) const -> std::string {
if (with_schema) {
return fmt::format("{} | {}{}", PlanNodeToString(), output_schema_, ChildrenToString(2, with_schema));
}
return fmt::format("{}{}", PlanNodeToString(), ChildrenToString(2, with_schema));
}
/** @return the cloned plan node with new children */
virtual auto CloneWithChildren(std::vector<AbstractPlanNodeRef> children) const
-> std::unique_ptr<AbstractPlanNode> = 0;
/**
* The schema for the output of this plan node. In the volcano model, every plan node will spit out tuples,
* and this tells you what schema this plan node's tuples will have.
*/
SchemaRef output_schema_;
/** The children of this plan node. */
std::vector<AbstractPlanNodeRef> children_;
protected:
/** @return the string representation of the plan node itself */
virtual auto PlanNodeToString() const -> std::string { return "<unknown>"; }
/** @return the string representation of the plan node's children */
auto ChildrenToString(int indent, bool with_schema = true) const -> std::string;
private:
};
} // namespace bustub
AbstractPlanNode
实现了通用的 PlanNode 的所有功能与特点. PlanNode 是按照树形结构组织的, 因此有下面的函数提供支持, GetChildAt()
, GetChildren()
, 用于获取孩子节点的信息, 这些信息在实现 Optimizer 部分十分有效.
Plan_Node 中还有表信息与 Schema 信息, 存储在 SchemaRef output_schema_
部分.
有些 Plan_Node 还有表达式部分, 例如 Fliter_Plan的谓语表达式:
/** The predicate that all returned tuples must satisfy */
AbstractExpressionRef predicate_;
这些与我们前面博客介绍的以及上述的 BUSTUB 的 EXPLAIN 部分的内容一致, 都包含了这些信息.
BUSTUB 中的 Plan_Node Executor
在 BUSTUB 中, Plan_Node 的每个 Executor 也都是从基类扩充实现的, 其中基类的实现如下:
namespace bustub {
class ExecutorContext;
/**
* The AbstractExecutor implements the Volcano tuple-at-a-time iterator model.
* This is the base class from which all executors in the BustTub execution
* engine inherit, and defines the minimal interface that all executors support.
*/
class AbstractExecutor {
public:
/**
* Construct a new AbstractExecutor instance.
* @param exec_ctx the executor context that the executor runs with
*/
explicit AbstractExecutor(ExecutorContext *exec_ctx) : exec_ctx_{exec_ctx} {}
/** Virtual destructor. */
virtual ~AbstractExecutor() = default;
/**
* Initialize the executor.
* @warning This function must be called before Next() is called!
*/
virtual void Init() = 0;
/**
* Yield the next tuple from this executor.
* @param[out] tuple The next tuple produced by this executor
* @param[out] rid The next tuple RID produced by this executor, A record identifier serves as a unique identifier for
* the tuple
* @return `true` if a tuple was produced, `false` if there are no more tuples
*/
virtual auto Next(Tuple *tuple, RID *rid) -> bool = 0;
/** @return The schema of the tuples that this executor produces */
virtual auto GetOutputSchema() const -> const Schema & = 0;
/** @return The executor context in which this executor runs */
auto GetExecutorContext() -> ExecutorContext * { return exec_ctx_; }
protected:
/** The executor context in which the executor runs */
ExecutorContext *exec_ctx_;
};
} // namespace bustub
在上述的代码中, 也可以看出, 由于 BUSTUB 使用的是 Iterator Model
, 因此最重要的就是 Next()
函数的实现, 在后续的代码中, 在每个 CPP
文件中, 有该函数的具体实现, 我们需要实现这些函数.
其中 tuple
是返回到父节点的 tuple, rid
是对应的 id, 指向该 tuple 的id.
总结
这部分是我已经实现了 Project3 一部分后, 回头看的时候总结意识到的, 这部分既是一个总结部分, 实际上也是具体实现的开头部分, 因为我们需要知道每个基类的每个函数的作用, 尤其是应该和 EXPLAIN
中的每一行对应, 既有利 DEBUG 检查错误, 也是一种保证.