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

【手写数据库内核miniToadb】第11天 打印解析树,SQL解析之后的变化

开源无限 2024-12-25
86

作者在数据库内核设计从业十余年;设计开发分布式数据内核;编写从零手写数据库教程;现在将过去的数据库内核经验一一分享,有兴趣的朋友加个关注。

一、概述 


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);

五、总结 


上一节生成了解析树,如何知道解析树是否正确,通过递归的遍历树中各节点,将它们的数据成员打印出来,就可以清晰的看到树的层次。

在后面随着解析树节点种类变多,树的结构也会越来越复杂,显示树的内容有助于我们调试解析树。

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

评论