在上篇关于SQL的流程实现大致说明后,我们来细化内部细节,例如列信息,表信息,再或是我们的记录是怎么传递的,以及到最后是怎么处理的。主要会展现表达式的细节和记录的细节。
同样会再同步介绍如何去拓展DML语句,DDL语句
表达式 Expression
表达式中最基础的就是值,因此我们要设计Value,
Value
值有众多不同的类型,例如int,float这种,对于不同类型的转换规则,或者是隐式转换等,需要有定制化的行为,因此需要设计一套专门的Value类。
值+类型(Tagged Union)
将实际数据存储在Union当中,节约内存,后续也可以在这里拓展类型。
AttrType attr_type_;
union Val { ... } value_;
union Val
{
int32_t int_value_;
date date_value_;
float float_value_;
bool bool_value_;
char *pointer_value_;
TextManager::TextMeta text_meta_value_;
float *vector_value_;
} value_ = {.int_value_ = 0};
提供计算+类型转换接口
static RC add(const Value &left, const Value &right, Value &result);
static RC subtract(const Value &left, const Value &right, Value &result);
static RC multiply(const Value &left, const Value &right, Value &result);
static RC divide(const Value &left, const Value &right, Value &result);
static RC negative(const Value &value, Value &result);
static RC cast_to(const Value &value, AttrType to_type, Value &result);
Value只负责提供接口,将实际的运算逻辑都交给DataType,DataType类似于一个方法类
DataType
class DataType
{
public:
explicit DataType(AttrType attr_type) : attr_type_(attr_type) {}
virtual ~DataType() = default;
inline static DataType *type_instance(AttrType attr_type)
{
return type_instances_.at(static_cast<int>(attr_type)).get();
}
inline AttrType get_attr_type() const { return attr_type_; }
virtual int compare(const Value &left, const Value &right) const { return INT32_MAX; }
virtual int compare(const Column &left, const Column &right, int left_idx, int right_idx) const { return INT32_MAX; }
virtual RC add(const Value &left, const Value &right, Value &result) const { return RC::UNSUPPORTED; }
virtual RC subtract(const Value &left, const Value &right, Value &result) const { return RC::UNSUPPORTED; }
virtual RC multiply(const Value &left, const Value &right, Value &result) const { return RC::UNSUPPORTED; }
virtual RC divide(const Value &left, const Value &right, Value &result) const { return RC::UNSUPPORTED; }
virtual RC negative(const Value &val, Value &result) const { return RC::UNSUPPORTED; }
virtual RC cast_to(const Value &val, AttrType type, Value &result) const { return RC::UNSUPPORTED; }
virtual RC to_string(const Value &val, string &result) const { return RC::UNSUPPORTED; }
virtual int cast_cost(AttrType type)
{
if (type == attr_type_) {
return 0;
}
return INT32_MAX;
}
virtual RC set_value_from_str(Value &val, const string &data) const { return RC::UNSUPPORTED; }
protected:
AttrType attr_type_;
static array<unique_ptr<DataType>, static_cast<int>(AttrType::MAXTYPE)> type_instances_;
};
每种值类型要自己继承一个类,例如CharType,FloatType,然后重载支持的运算方法,将所有逻辑都存储在自己的Type类实现中,Value会通过type_instance的方式来自动获取应该调用的方法,同样,遇到一些隐式类型转换也应在其中处理。
Expression
我们有了不同的值和其类型,并且已经处理了所有的类型转换,接下来就应该设计表达式了。
class Expression
{
public:
Expression() = default;
virtual ~Expression() = default;
virtual unique_ptr<Expression> copy() const = 0;
virtual bool equal(const Expression &other) const { return false; }
virtual RC get_value(const Tuple &tuple, Value &value) const = 0;
virtual RC get_values(const Tuple &tuple, vector<Value> &values) const = 0;
virtual RC try_get_value(Value &value) const { return RC::UNIMPLEMENTED; }
virtual RC open(Trx* trx) { return RC::SUCCESS; }
virtual RC close() { return RC::SUCCESS; }
virtual RC get_column(Chunk &chunk, Column &column) { return RC::UNIMPLEMENTED; }
virtual RC traverse_check(const std::function<RC(Expression*)>& check_func, function<bool(Expression*)> filter = [](Expression*expr){return true;})
{
if(not filter(this)){
return RC::SUCCESS;
}
return check_func(this);
}
virtual ExprType type() const = 0;
virtual AttrType value_type() const = 0;
virtual int value_length() const { return -1; }
virtual const char *name() const { return name_.c_str(); }
virtual std::string alias() const { return alias_; }
virtual void set_name(string name) { name_ = name; }
virtual void set_alias(string alias) { alias_ = alias; }
virtual int pos() const { return pos_; }
virtual void set_pos(int pos) { pos_ = pos; }
virtual RC eval(Chunk &chunk, vector<uint8_t> &select) { return RC::UNIMPLEMENTED; }
protected:
/**
* @brief 表达式在下层算子返回的 chunk 中的位置
* @details 当 pos_ = -1 时表示下层算子没有在返回的 chunk 中计算出该表达式的计算结果,
* 当 pos_ >= 0时表示在下层算子中已经计算出该表达式的值(比如聚合表达式),且该表达式对应的结果位于
* chunk 中 下标为 pos_ 的列中。
*/
int pos_ = -1;
private:
string name_;
string alias_;
};
在SQL语句中,表达式有非常多种形式,例如
enum class ExprType
{
NONE,
STAR, ///< 星号,表示所有字段
UNBOUND_FIELD, ///< 未绑定的字段,需要在resolver阶段解析为FieldExpr
UNBOUND_AGGREGATION, ///< 未绑定的聚合函数,需要在resolver阶段解析为AggregateExpr
FIELD, ///< 字段。在实际执行时,根据行数据内容提取对应字段的值
VALUE, ///< 常量值
CAST, ///< 需要做类型转换的表达式
COMPARISON, ///< 需要做比较的表达式
CONJUNCTION, ///< 多个表达式使用同一种关系(AND或OR)来联结
ARITHMETIC, ///< 算术运算
AGGREGATION, ///< 聚合运算
SYSFUNCTION, ///< 函数(length、round、date_format)
SUBQUERY, ///< 子查询
EXPRLIST, ///< 表达式集合
};
对于任意表达式都可以用我们这个Expr来构建,例如 (col1 + col2) - 114 < col3
COMPARISON (<)
/ \\
/ \\
ARITHMETIC (-) FIELD(col3)
/ \\
/ \\
ARITHMETIC(+) VALUE(114)
/ \\
/ \\
FIELD(col1) FIELD(col2)
其中我们可以看到,例如 FieldExpr,实际上是 select col from 中的 col , 至于为什么会有UNBOUND_FIELD,因为在初步语法解析SQL的时候只有文本,得到语法解析后的 ParsedSql 来容纳各个字符串。
下一步就是绑定字符串:将表名绑定到表对象,字段名绑定到对应column等等。
这一步就需要将Expr进行一个遍历,这里可以使用上面设计的 traverse_check ,会不断递归调用进行一个 check , 并且将 check 的行为以及 需要 check 的节点过滤作为两个 callback 传入,更加灵活处理。
获取表达式的值
获取表达式的值有两种方式,get_value 和 try_get_value,分别是将一行 Tuple 传入后计算得到的值,和传入 Tuple 不被使用的纯数字表达式。
对于任意一个Expr,当 get_value 获取值的时候,也会从左右两个表达式种 get_value 得到结果,返回出结果值。
例如 FieldExpr
RC FieldExpr::get_value(const Tuple &tuple, Value &value) const
{
return tuple.find_cell(TupleCellSpec(table_name(), field_name()), value);
}
例如 CastExpr
RC CastExpr::get_value(const Tuple &tuple, Value &result) const
{
Value value;
RC rc = child_->get_value(tuple, value);
if (rc != RC::SUCCESS) {
return rc;
}
if(value.is_null()){
result = std::move(value);
return RC::SUCCESS;
}
return cast(value, result);
}
SYSFUNCTION
函数表达式是很重要的一点,例如length、round、date_format或者是后续的向量计算等等函数,将参数定义为 std::vector<std::unique_ptr<Expression>> params_; 即可获取动态数量的参数进行读取和运算。
RC try_get_func_length_value(Value &value) const;
RC get_func_round_value(const Tuple &tuple, Value &value) const;
RC try_get_func_round_value(Value &value) const;
RC get_func_date_format_value(const Tuple &tuple, Value &value) const;
RC try_get_func_date_format_value(Value &value) const;
RC get_func_vector_to_string_value(const Tuple &tuple, Value &value) const;
RC try_get_func_vector_to_string_value(Value &value) const;
RC get_func_string_to_vector_value(const Tuple &tuple, Value &value) const;
RC try_get_func_string_to_vector_value(Value &value) const;
RC get_func_distance_value(const Tuple &tuple, Value &value) const;
RC try_get_func_distance_value(Value &value) const;
RC get_value(const Tuple &tuple, Value &value) const override{
RC rc = RC::SUCCESS;
switch(func_type_){
case SYS_FUNC_LENGTH: {
rc = get_func_length_value(tuple, value);
break;
}
case SYS_FUNC_ROUND: {
rc = get_func_round_value(tuple, value);
break;
}
case SYS_FUNC_DATE_FORMAT: {
rc = get_func_date_format_value(tuple, value);
break;
}
case SYS_FUNC_VECTOR_TO_STRING: {
rc = get_func_vector_to_string_value(tuple, value);
break;
}
case SYS_FUNC_STRING_TO_VECTOR: {
rc = get_func_string_to_vector_value(tuple, value);
break;
}
case SYS_FUNC_DISTANCE: {
rc = get_func_distance_value(tuple, value);
break;
}
以上只需要定义自己的函数,以及运算方法即可扩展任意的函数表达式,使得自定义函数生效。
记录 Record&Tuple
Record存储了记录的字节流,Tuple可以读取信息
Record
即一块内存管理类,可以复制读改。支持深拷贝/移动语义,可直接引用页面数据(需持有锁)或复制独立内存副本。按字节偏移访问。
以字节流为基础,不展现其他例如管理实际数据为能力,是存储格式。
Tuple
Tuple (抽象基类)
│
┌────────┼────────┐
│ │ │
RowTuple ProjectTuple ValueListTuple
│
│
JoinedTuple
Tuple 是 SQL 层的逻辑行抽象,表示查询结果中的一行数据。可以理解为带类型信息的 Value。
按字段名/索引访问,是逻辑视图。
| 类型 | 作用 | 关键特点 |
|---|---|---|
| Tuple | 抽象基类 | 定义 cell_num(), cell_at(), find_cell() 接口 |
| RowTuple | 直接映射表的记录 | 持有 Record*,通过 FieldMeta 解析字段,支持 NULL bitmap |
| ProjectTuple | 投影操作 | 持有 Expression 列表,支持表达式计算、类型转换、重命名 |
| ValueListTuple | 常量值组成的行 | 存储 vector<Value>,用于字面量结果 |
| JoinedTuple | 连接两个 Tuple | 持有左右 Tuple*,用于 JOIN 操作 |
RowTuple(数据基于Record*)
RowTuple 持有 Record*,通过 FieldMeta 将二进制数据解析为 Value。
还持有每个 Value 的 FieldMeta ,知道每个类型的数据类型,可设置表头。
支持通过 cell_at 下标查询值,通过 spec_at 查询列名表名别名等
支持通过 find_cell 列名表名别名来查询值
顺便可以使用 bitmap_ 来记录null值
ProjectTuple(数据基于Tuple)
ProjectTuple 对 Tuple 做投影/表达式计算。通常是 Select 行为的逻辑运算。
持有下层包含 Value 的 Tuple,持有 投影信息的 vector<unique_ptr
通过投影信息,获取持有的 Tuple其中数据,计算投影后的值,作为自己的 cell 值
ValueListTuple(数据基于Value)
存储已计算的 Value 列表,用于物化(materialize)中间结果。
持有 vector
更多作为Value的容器,像RowTuple一定要持有Record,然后计算的中间值只会有Value。
JoinedTuple(数据基于Tuple)
将两个tuple合并为一个tuple,用于 Join 算子的结果,让 Join 算子有结果值的容器,其持有两个Tuple,分别是 左 和 右。
其查询操作也就是在两者中轮流查询。
Tuple的额外设计
Tuple不单可以存储我们记录行中所见的数据,也可以将记录行的事务相关信息一并存储,事务相关信息可以从Tuple中轻易读取,并且 Select 都是投影操作,不会读取事务相关信息输出展示
例如 Select 中的 * 表达式,其会忽略所有不可见列。
static void wildcard_fields(Table *table,
vector<unique_ptr<Expression>> &expressions) {
const TableMeta &table_meta = table->table_meta();
// 跳过系统字段(如事务字段)
for (int i = table_meta.sys_field_num(); i < field_num; i++) {
Field field(table, table_meta.field(i));
FieldExpr *field_expr = new FieldExpr(field);
field_expr->set_name(field.field_name());
expressions.emplace_back(field_expr); // 添加到投影列表
}
}
此处将所有系统字段例如事务字段放在Tuple的最前面,通过 sys_field_num 获取第一个非系统字段。
事务字段设计
在 MVCC 事务模式下,每条记录会携带以下隐藏字段:
FieldMeta("__trx_xid_begin", AttrType::INTS, 0, 4, false, -1), // 创建事务ID
FieldMeta("__trx_xid_end", AttrType::INTS, 0, 4, false, -2), // 删除事务ID
这些字段存储在 Record 中:作为每行数据的组成部分,占用磁盘空间
标记为 visible=false:用户不可见,SELECT 不会返回。且用于 MVCC 可见性判断:判断记录对当前事务是否可见
SQL 层通过 Tuple 访问数据时,事务字段对用户查询完全透明,并且事务信息和用户数据存储在同一 Record 中,避免额外的间接寻址。