本文介绍如何新增一种类型的SQL语句。
当前的SQL实现已经比较复杂,这里以新增一个简单的SQL语句为例,介绍如何新增一种类型的SQL语句。 在介绍如何新增一种类型的SQL语句之前,先介绍一下MiniOB的SQL语句的执行流程。
SQL语句执行流程¶
MiniOB的SQL语句执行流程如下图所示:
左侧是执行流程节点,右侧是各个执行节点输出的数据结构。
┌──────────────────┐ ┌──────────────────┐
│ SQL │ ---> │ String │
└────────┬─────────┘ └──────────────────┘
│
┌────────▼─────────┐ ┌──────────────────┐
│ Parser │ ---> │ ParsedSqlNode |
└────────┬─────────┘ └──────────────────┘
│
│
┌────────▼─────────┐ ┌──────────────────┐
│ Resolver │ ---> │ Statement │
└────────┬─────────┘ └──────────────────┘
│
│
┌────────▼─────────┐ ┌──────────────────┐
│ Transformer │ ---> │ LogicalOperator |
└────────┬─────────┘ │ PhysicalOperator │
│ │ or │
┌────────▼─────────┐ │ CommandExecutor |
│ Optimizer │ ---> │ │
└────────┬─────────┘ └──────────────────┘
│
┌────────▼─────────┐ ┌──────────────────┐
│ Executor │ ---> │ SqlResult │
└──────────────────┘ └──────────────────┘
- 我们收到了一个SQL请求,此请求以字符串形式存储;
- 在Parser阶段将SQL字符串,通过词法解析(lex_sql.l)与语法解析(yacc_sql.y)解析成ParsedSqlNode(parse_defs.h);
- 在Resolver阶段,将ParsedSqlNode转换成Stmt(全称 Statement, 参考 stmt.h);
- 在Transformer和Optimizer阶段,将Stmt转换成LogicalOperator,优化后输出PhysicalOperator(参考 optimize_stage.cpp)。如果是命令执行类型的SQL请求,会创建对应的 CommandExecutor(参考 command_executor.cpp);
- 最终执行阶段 Executor,工作量比较少,将PhysicalOperator(物理执行计划)转换为SqlResult(执行结果),或者将CommandExecutor执行后通过SqlResult输出结果。
新增一种类型的SQL语句¶
这里将以CALC类型的SQL为例介绍。
CALC 不是一个标准的SQL语句,它的功能是计算给定的一个四则表达式,比如:
CALC 1+2*3
CALC 在SQL的各个流程中,与SELECT语句非常类似,因此在增加CALC语句时,可以参考SELECT语句的实现。
首先在Parser阶段,我们需要考虑词法分析和语法分析。CALC 中需要新增的词法不多,简单的四则运算中只需要考虑数字、运算符号和小括号即可。在增加CALC之前,只有运算符号没有全部增加,那我们加上即可。参考 lex_sql.l
。
"+" |
"-" |
"*" |
"/" { return yytext[0]; }
在语法分析阶段,我们参考SELECT相关的一些解析,在yacc_sql.y中增加CALC的一些解析规则,以及在parse_defs.h中SELECT解析后的数据类型,编写CALC解析出来的数据类型。 当前CALC是已经实现的,可以直接在parse_defs.h中的类型定义。 yacc_sql.y中需要增加calc_stmt,calc_stmt的类型,以及calc_stmt的解析规则。由于calc_stmt涉及到表达式运算,所以还需要增加表达式的解析规则。具体可以参考yacc_sql.y中关于calc的代码。
在语法解析结束后,输出了CalcSqlNode
,后面resolver阶段,将它转换为Stmt,这里就是新增CalcStmt
。通常在resolver阶段会校验SQL语法树的合法性,比如查询的表是否存在,运算类型是否正确。在CalcStmt
中,逻辑比较简单,没有做任何校验,只是将表达式记录下来,并且认为这里的表达式都是值类型的计算。
在Transformer和Optmize阶段,对于查询类型的SQL会生成LogicalOperator和PhysicalOperator,而对于命令执行类型的SQL会生成CommandExecutor。CALC是查询类型的SQL,参考SELECT的实现,在SELECT中,有PROJECT、TABLE_SCAN等类型的算子,而CALC比较简单,我们新增CalcLogicalOperator
和CalcPhysicalOperator
。
由于具有执行计划的SQL,在Executor阶段,我们仅需要给SqlResult设置对应的TupleSchema即可,可以参考ExecuteStage::handle_request_with_physical_operator
。
总结一下,新增一种类型的SQL,需要在以下几个地方做修改: 1. 词法解析,增加新的词法规则(lex_sql.l); 2. 语法解析,增加新的语法规则(yacc_sql.y); 3. 增加新的SQL语法树类型(parse_defs.h); 4. 增加新的Stmt类型(stmt.h); 5. 增加新的LogicalOperator和PhysicalOperator(logical_operator.h, physical_operator.h, optimize_stage.cpp,如果有需要); 6. 增加新的CommandExecutor(command_executor.cpp,如果有需要); 7. 设置SqlResult的TupleSchema(execute_stage.cpp,如果有需要)。