在上篇关于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。

还持有每个 ValueFieldMeta ,知道每个类型的数据类型,可设置表头。

支持通过 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 ,使用场景例如 作为 GROUP BY 分组的 Key,缓存所有行用于排序,缓存多个子查询的结果等功能。

更多作为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 中,避免额外的间接寻址。