作者在数据库内核设计从业十余年;设计开发分布式数据内核;编写从零手写数据库教程;现在将过去的数据库内核经验一一分享,有兴趣的朋友加个关注。
一、概述
SQL的解析过程中,难免遇到输出的解析树有偏差的时候,可能是词法分析中正则表达式编写的有误,也或许是语法规则中优先级存在问题,避免问题漫延到更多模块,好的办法是在每个模块都有定义一些测试用例,覆盖设计目标中的要求。
解析树是解析模块的最终输出结果,也是下一个模块的输入,是非常关键的信息,在这里可以将它以树的层次的形式打印出来,方便测试的观察与调试。
首先定义一个打印解析树的基本框架,将当前解析树以Node指针参数传入,根据节点的类型分发到各类型的打印处理函数,可能在节点中也会递归调用节点分发处理下一层的子树。
为了显示节点的层次,在分发时增加一个层级的参数,每次在节点中需要再次分发节点时,就将节点层数递增。更进一步显示格式化,每层的打印内存都按层级数进行缩进,这样在浏览时能清晰看出层级的联系。
二、分发节点处理
以深度优先遍历解析树中的各节点来完成解析树的打印,而对于解析树中的每个节点,它们有各自的节点信息显示函数,因此分发函数只需要遍历节点,调用节点的显示函数即可。
static void ShowNode(Node* n, int level)
{
if(NULL == n)
{
printf("node is null\n");
return;
}
/* list node show */
ShowBlank(level);
printf("{ \n");
switch(n->type)
{
case T_List:
TravelListNode((List*)n, level);
break;
case T_CreateStmt:
ShowNodCreateStmt((CreateStmt*)n, level);
break;
case T_ColumnDef:
ShowNodColumnDef((ColumnDef*)n, level);
break;
default:
ShowBlank(level);
break;
}
ShowBlank(level);
printf("} \n");
}
在分发函数中,开头和结尾以大括号包围,来标示当前的层次范围,当然层次也能以空格缩进的程序体现。
三、节点显示处理函数
每种类型的节点,数据成员各不相同,每定义一个类型节点时,就需要添加对应的信息打印函数,打印当前节点的各成员,当成员为节点类型时,就要调用节点分发函数来递归处理。
3.1 列表节点
链表节点的打印函数中,除了打印链表长度,遍历链表中各节点,递归调用分发函数进行处理。
static void TravelListNode(List* node, int level)
{
ListNode* nextNode = NULL;
if(NULL == node)
{
printf("list node is null\n");
return;
}
ShowBlank(level+1);
printf("length:%d \n", node->length);
/* list cell node show */
for(nextNode = node->head; nextNode != NULL; nextNode = nextNode->next)
{
Node* node = (Node*)nextNode->value;
ShowNode(node, level+1);
}
return;
}
3.2 创建表节点
创建表的节点,打印表名,以及显示属性列定义列表,它递归调用。
static void ShowNodCreateStmt(CreateStmt* node, int level)
{
ShowBlank(level+1);
printf("tablename:%s \n", node->tableName);
ShowNode(node->colList, level+1);
return;
}
3.3 列属性定义节点
属性列定义节点的显示,主要显示属性列名和属性列的类型。
static void ShowNodColumnDef(ColumnDef* node, int level)
{
ShowBlank(level+1);
printf("colName:%s \n", node->colName);
ShowBlank(level+1);
printf("colType:%s \n", node->colType);
return;
}
每个节点的打印函数的声明形式,风格尽量保持统一,参数为当前节点类型和层级。
四、解析树显示函数
在每次完成SQL解析时,可以将解析模块返回的解析树根节点传入遍历打印,其实树根也是一个节点类型的节点,直接调用节点分发函数处理,根的层级为1。
void travelParserTree(Node* root)
{
printf("parser tree :\n");
ShowNode(root, 1);
return ;
}
最后在主函数中调用,在生成解析树之后,开始递归打印各节点的内容。
yyparse(scannerinfo);
travelParserTree(pt.root);
五、总结
上一节生成了解析树,如何知道解析树是否正确,通过递归的遍历树中各节点,将它们的数据成员打印出来,就可以清晰的看到树的层次。
在后面随着解析树节点种类变多,树的结构也会越来越复杂,显示树的内容有助于我们调试解析树。




