zoukankan      html  css  js  c++  java
  • PostgreSQL源码解读 基础结构 node

    一、node节点的定义

    源代码路径postgresql-9.2.3/src/include/nodes/nodes.h

    在查询解析SQL的查询部分,要用到大量的结构体,许多函数处理的逻辑类似,就是传入的结构体不同,为了处理这个问题,pg采用了一个基础结构体struct node,其他结构体的第一个字段与node的相同。通过这个字段来标识不同的结构体,而又同时能统一接口函数。

    pg主要采用c实现,因此没有采用多态。(顺带说一句,之前一直以为MySQL的代码都是由c实现的,实际上,MySQL中也有部分是由C++实现的,比如它的查询解析部分,就使用了C++实现,而且大量采用了继承,模版,容器(MySQL自己实现的)等特性)。

    Node的定义如下:

    typedef struct Node

    {

        NodeTag        type;

    } Node;

    其他的节点也有类似定义,如常量的定义:

    typedef struct A_Const

    {

        NodeTag        type;

        Value            val;        /* 值类型 */

        int            location;    /* 的位置,未知时赋值为-1 */

    } A_Const;

    每个节点的第一个字段都是NodeTag.使用makeNode函数生成的每一个节点结构的第一个字段都会被赋值为枚举类型的NodeTag的一个值。NodeTag的定义如下:

    typedef enum NodeTag

    {

        T_Invalid = 0,

     

        /*

         * TAGS FOR EXECUTOR NODES (execnodes.h)

         */

        T_IndexInfo = 10,

        T_ExprContext,

        T_ProjectionInfo,

        T_JunkFilter,

    } NodeTag;

    NodeTag是个枚举类型,包含约300个左右的枚举值,每个枚举值代表了一个结构体,篇幅限制,省略了其中的大部分。这些枚举值的数字是不连续,主要为方便以后添加新的结构体类型。每个节点的第一个字段都是NodeTag,它们可以在传递指针是都转为Node *结构,然后在根据NodeTag的值进行区别处理,这样做最大的好处就是能是函数接口统一。而且使用Node*定义变量比使用void *更好调试。

     

    二、node节点的创建及释放

     

    makeNode 是一个宏,用来创建一个节点并为该节点设置一个tag值。

    #define makeNode(_type_)        ((_type_ *) newNode(sizeof(_type_),T_##_type_))

     

    实际调用的则是另一个宏newNode,newNode则有两个版本,一个是针对gcc编译器,一个是针对g++编译器

    #ifdef __GNUC__

     

    /* 针对gcc版本的newNode */

    #define newNode(size, tag)

    ({    Node *_result;

        AssertMacro((size) >= sizeof(Node));/* 检测申请的内存大小,>>=sizeof(Node) */

        _result = (Node *) palloc0fast(size); /* 申请内存 */

        _result->type = (tag); /*设置TypeTag */

        _result; /*返回值*/

    })

    #else

     

    /*

        针对g++编译器版本的newNode,区别在于,g++版本的返回的指针要用全局变量

    */

    extern PGDLLIMPORT Node *newNodeMacroHolder;

     

    #define newNode(size, tag)

    (

        AssertMacro((size) >= sizeof(Node)),        /* need the tag, at least */

        newNodeMacroHolder = (Node *) palloc0fast(size),

        newNodeMacroHolder->type = (tag),

        newNodeMacroHolder

    )

    #endif /* __GNUC__ */

     

    可以看出,创建一个新节点是通过两个宏makeNode,和newNode完成的。

    注意:要避免直接使用newNode来创建节点,因为节点的大小在不同的环境下可能是不同的。使用makeNode即可,如:

    Stmt *s = makeNode(Stmt);

     

    释放节点很简单,创建时makeNode是使用palloc(相当于malloc)创建,直接使用pg中的pfree函数释放即可。

        pfree(s);

    如果忘记释放也没关系,pg使用的内存上下文,能够自动的释放掉这些指针。

     

    三、node节点的常用函数

     

        node相关的函数包括

    1. nodeTag(nodeptr) 返回该节点对于的枚举值

      实际上是一个宏

          #define nodeTag(nodeptr)        (((const Node*)(nodeptr))->type)

    2. IsA(nodeptr,type) 判断某个节点指针指向的结构体是否是type类型,是就返回true,否则返回flase,实际上也是一个宏

          #define IsA(nodeptr,_type_)        (nodeTag(nodeptr) == T_##_type_)

    3. equal(const void *a,const void *b) 判断两个结构体是否相等,是就返回true,否则返回false.

      该函数的主要逻辑为:

      bool equal(const void *a, const void *b)

    {

        bool        retval;

     

        if (a == b) /*指向相同的结构体*/

            return true;

     

        /*如果 a!=b, 则他们只有一个可以为NULL*/

        if (a == NULL || b == NULL)

            return false;

     

        /*是否是相同的结构类型 */

        if (nodeTag(a) != nodeTag(b))

            return false;

     

        switch (nodeTag(a))

        {

                /* 基础节点的类型比较 */

            case T_Alias:

                retval = _equalAlias(a, b);/*两个Alias结构体的比较*/

                break;

            case T_RangeVar:

                retval = _equalRangeVar(a, b);/*两个RangeVar结构体的比较*/

                break;

            …/*其他类型的结构体的比较*/

    }

    return retval;

    }

        前面说过,backendNodeTag枚举的结构体大约是300个左右,每个结构体都要定义一个比较函数,因此这个equal函数的实现很长。最终由equal函数统一对外的接口。可见,代码开发不仅需要技巧,也需要很大的耐心。

     

    四、再说Node节点的设计

     

        在pg的查询解析部分,包括查询解析,查询编译,查询重写,生成计划,制定执行路径等步骤。各个结构体都是一个特殊的节点,由NodeTag来标识。如果用面向对象的思维来理解的话,可以简单的看成是很多的特殊子节点继承自一个同一个父节点。这种设计并没有减少代码量,但是可以使函数拥有统一的对外接口,更容易书写成文档。相比void *,更能够提高调试能力。

        除了基本的node的节点设计,pg中还有可计算表达式树也采用了类似的方式实现,表示式树中的每个节点的第一个字段都是一个Expr类型的枚举值。

  • 相关阅读:
    不使用border-radius,实现一个可复用的高度和宽度都自适应的圆角矩形
    实现一个div在浏览器水平居中
    HTML画布(2)
    《10X工作法制》笔记
    消息体的上传格式
    在rpc链路中的工作总结
    同属姓名注入spring报错
    《事实》听后感
    jdk8使用stream对指定值去重以及其他stream用法
    如何写好业务(待续)
  • 原文地址:https://www.cnblogs.com/chenxueyou/p/3412833.html
Copyright © 2011-2022 走看看