暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【手写数据库核心揭秘系列】第84讲 表达式的运算,表达式类型与实现框架

开源无限 2025-04-17
44

💻 深耕数据库内核架构设计与开发十余年,曾主导多款高性能分布式数据库内核研发,攻克高并发、低延迟等核心技术难题。现倾力打造《从零手写数据库》系列教程,首次系统性公开数据库内核源码级实现细节!

🚀 从存储引擎、查询优化到分布式事务,手把手拆解核心模块;从语法解析树构建到执行计划生成,逐行代码还原设计精髓
🌟 无论你是数据库开发者、系统架构师,还是对底层技术充满好奇的极客,这里都有你想要的“硬核干货”!点击关注,与行业老兵共同探索数据库技术的星辰大海!

一、概述 


在查询SQL中有一个重要的组成部分,就是表达式,它在select子句,from子句,where子句中都会出现,在不同的位置代表的含义也不相同。

通常表达式有以下几种类型:

  • 标量表达式,返回单个值的表达式,通常用于操作单行数据中的字段或常量。
  • 条件表达式,根据条件逻辑返回不同的值或执行分支操作。
  • 逻辑表达式,组合布尔条件,通常用AND、OR、NOT等连接多个表达式,用于 WHERE、HAVING 或 CHECK 约束。
  • 聚合表达式,对多行数据进行汇总计算,返回单个汇总值。
  • 窗口函数表达式,在数据窗口(一组相关行)内进行计算,保留原始行的同时返回聚合结果。
  • 表表达式,也就是子查询,返回一个虚拟表的表达式,通常用于复杂查询的模块化。
  • 类型转换表达式,显式或隐式转换数据类型。

在前面的基础之上,本节来实现对表达式的处理,主要实现前三种类型的处理。

二、基本表达式运算 


参与表达式运算的基础数据类型的数值,不同类型的数值支持不同的运算符,比如整型数值,可以支持算术运算,比较运算;而字符类型的数值,支持比较运算,不支持算术运算。

表达式经过解析之后,是一棵二叉树,每个操作对应具体的两个数值,当数据值的类型支持相应操作时才能进行运算,如果数据类型不支持相应操作时,会产生错误。

当两个操作数类型不相同时,就不能进行直接运算,一般会将右操作数的类型转换为左操作数相同的类型,如果右操作符的数据类型支持类型转换运算时,则可以自动隐式的转换,否则必须由SQL中指定转换。

最基本的表达式运算就是两个数据值的运算,根据数据类型,分别实现它们支持的运算操作。

三、数据类型的运算符集


每种数据类型都有一组支持的运算符函数,为了每种数据类型都可以统一调用,实现运算集的基本框架。


 3.1 运算符函数 

对于一元和二元操作,即有一个操作数或两个操作数的运算符,定义通用的操作运算函数。

typedef ValuesData* (*Operator2)(ValuesData* leftvalue, ValuesData* rightvalue);


 3.2 运算符集 

每种数据类型都支持一组操作运算,由统一的结构来表示。

typedef struct DataTypeProcs
{

    int         version;
    DataType    dType;
    Operator2   opf2[EXPR_OP_NUM];
}DataTypeProcs;

成员说明:

  • dType, 数据类型,当前数据库支持的基本数据类型;
  • opf2, 二元操作函数数组,数据大小为当前数据库支持的运算类型最大值,每个数据成员为对应的操作函数;操作的类型在node.h中已经定义。


 3.3 表达式运算 

当一个运算节点需要计算表达式值时,调用基础的二元表达式计算函数即可。

ValuesData* ComputeExpr(Expr_Op opType, ValuesData *lres, ValuesData *rres)
{
    ValuesData *result = NULL;

    if(lres == NULL)
        returnNULL;
    
    if(dTypesProc[lres->dataType] == NULL)
    {
        /* not support */
        returnNULL;
    }
    else
    {
        if((rres != NULL) && (rres->dataType != lres->dataType))
        {
            printf("right operand may be ocur type conversation.\n");
   returnNULL;
        }

        result = dTypesProc[lres->dataType]->opf2[opType](lres, rres);
    }

    return result;
}

函数流程如下:

  1. 函数参数传入操作符类型,左右操作数,左操作数不能为空,当然一元操作时,右操作数为空;
  2. 当左右操作数的数据类型不一致时,右操作数可能会发生类型转换;
  3. 调用左操作数对应的运算函数进行表达式计算;如果操作数不支持类型转换时,就会产生错误。

其中dTypesProc是一个运算符集合的结构体的数组,数据大小为数据类型的个数,每种数据类型将它支持的运算函数集合结构加到数组中,通过数据类型进行索引,再按操作符类型索引就可以找到对应的运算函数。

DataTypeProcs* dTypesProc[DT_END] =
{
    &integerDataTypeProcEntry,
    &FloatDataTypeProcEntry,
    &CharDataTypeProcEntry,
    &BoolDataTypeProcEntry,
    &VarcharDataTypeProcEntry,
    &TextDataTypeProcEntry
};

目前没有隐式类型转换实现,当数据类型不一致时无法运算。

 四、总结 


新增表达式模块,对应的代码位于exam_49目录的expr.h/expr.c文件当中。


🌟 点赞收藏,分享给身边的技术伙伴,关注我们,持续获取数据库内核开发的硬核干货!一起从源码级实现到分布式架构,解锁数据库技术的每一个核心细节!🚀
【往期精彩推荐】

【手写数据库核心揭秘系列】83 查询执行器,表数据查询展示

【手写数据库核心揭秘系列】81 别名查询,相同数据表却成为不同的数据源表

【手写数据库内核miniToadb】第81讲 数据字典的持久化存储


文章转载自开源无限,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论