openGauss数据库源码解析 | SQL引擎源解析(5)

6.2.3  语义分析

语义分析模块在词法分析和语法分析之后执行,用于检查SQL命令是否符合语义规定,能否正确执行。负责语义分析的是parse_analyze函数,位于analyze.cpp下。parse_analyze会根据词法分析和语法分析得到的语法树,生成一个ParseState结构体用于记录语义分析的状态,再调用transformStmt函数,根据不同的命令类型进行相应的处理,最后生成查询树。

ParseState保存了许多语义分析的中间信息,如原始SQL命令、范围表、连接表达式、原始WINDOW子句、FOR UPDATE/FOR SHARE子句等。该结构体在语义分析入口函数parse_analyze下被初始化,在transformStmt函数下根据不同的Stmt存储不同的中间信息,完成语义分析后再被释放。ParseState结构如下。

struct ParseState {
    struct ParseState* parentParseState; // 指向外层查询
    const char* p_sourcetext; // 原始SQL命令
    List* p_rtable; // 范围表
    List* p_joinexprs;  // 连接表达式
List* p_joinlist;  // 连接项
    List* p_relnamespace; // 表名集合
    List* p_varnamespace; // 属性名集合
    bool  p_lateral_active;
    List* p_ctenamespace; // 公共表达式名集合
    List* p_future_ctes; // 不在p_ctenamespace中的公共表达式
    CommonTableExpr* p_parent_cte;
    List* p_windowdefs; // WINDOW子句的原始定义
    int p_next_resno; // 下一个分配给目标属性的资源号
    List* p_locking_clause; // 原始的FOR UPDATE/FOR SHARE信息
    Node* p_value_substitute;
    bool p_hasAggs; // 是否有聚集函数
    bool p_hasWindowFuncs; // 是否有窗口函数
    bool p_hasSubLinks; // 是否有子链接
    bool p_hasModifyingCTE; 
    bool p_is_insert; // 是否为INSERT语句
    bool p_locked_from_parent;
    bool p_resolve_unknowns;
    bool p_hasSynonyms;
    Relation p_target_relation; // 目标表
    RangeTblEntry* p_target_rangetblentry; // 目标表在RangeTable对应的项
……
};

在语义分析过程中,语法树parseTree使用Node节点进行包装。Node结构只有一个类型为NodeTag枚举变量的字段,用于识别不同的处理情况。比如SelectStmt 对应的NodeTag值为T_SelectStmt。Node结构如下。

typedef struct Node {
    NodeTag type;
} Node;

 

transformStmt函数会根据NodeTag的值,将语法树转化为不同的Stmt结构体,调用对应的语义分析函数进行处理。openGauss在语义分析阶段处理的NodeTag情况有九种,详细请参考表6-4。

表6-4  NodeTag情况说明

NodeTag

语义分析函数

说明

T_InsertStmt

transformInsertStmt

处理INSERT语句的语义

T_DeleteStmt

transformDeleteStmt

处理DELETE语句的语义

T_UpdateStmt

transformUpdateStmt

处理UPDATE语句的语义

T_MergeStmt

transformMergeStmt

处理MERGE语句的语义

T_SelectStmt

transformSelectStmt

处理基本SELCET语句的语义

transformValuesClause

处理SELCET VALUE语句的语义

transformSetOperationStmt

处理带有UNION、INTERSECT、EXCEPT的SELECT语句的语义

T_DeclareCursorStmt

transformDeclareCursorStmt

处理DECLARE语句的语义

T_ExplainStmt

transformExplainStmt

处理EXPLAIN语句的语义

T_CreateTableAsStmt

transformCreateTableAsStmt

处理CREATE TABLE AS,SELECT INTO和CREATE MATERIALIZED VIEW等语句的语义

其他

--

作为UTILITY类型处理,直接在分析树上封装Query返回

以处理基本SELECT命令的transformSelectStmt函数为例,其处理流程如下。

(1) 创建一个新的Query节点,设置commandType为CMD_SELECT。

(2) 检查SelectStmt是否存在WITH子句,存在则调用transformWithClause处理。

(3) 调用transformFromClause函数处理FROM子句。

(4) 调用transformTargetList函数处理目标属性。

(5) 若存在操作符“+”则调用transformOperatorPlus转为外连接。

(6) 调用transformWhereClause函数处理WHERE子句和HAVING子句。

(7) 调用transformSortClause函数处理ORDER BY子句。

(8) 调用transformGroupClause函数处理GROUP BY子句。

(9) 调用transformDistinctClause函数或者transformDistinctOnClause函数处理DISTINCT子句。

(10) 调用transformLimitClause函数处理LIMIT和OFFSET子句。

(11) 调用transformWindowDefinitions函数处理WINDOWS子句。

(12) 调用resolveTargetListUnknowns函数将其他未知类型作为text处理。

(13) 调用transformLockingClause函数处理FOR UPDATE子句。

(14) 处理其他情况,如insert语句、foreign table等。

(15) 返回查询树。

下面对FROM子句、目标属性、WHERE子句的语义分析过程进行说明,SELECT语句的其他部分语义分析方式与此类似,不做赘述。

处理目标属性的入口函数是transformTargetList,函数的传参包括结构体ParseState和目标属性链表targetlist。transformTargetList会调用transformTargetEntry来处理语法树下目标属性的每一个ListCell,最终将语法树ResTarget结构体的链表转换为查询树TargetEntry结构体的链表,每一个TargetEntry表示查询树的一个目标属性。

TargetEntry结构如下。其中resno保存目标属性的编号(从1开始计数),resname保存属性名,resorigtbl和resorigcol分别保存目标属性源表的OID和编号。

typedef struct TargetEntry {
    Expr xpr;
    Expr* expr;            // 需要计算的表达式
    AttrNumber resno;      // 属性编号
    char* resname;         // 属性名
    Index ressortgroupref; // 被ORDER BY和GROUP BY子句引用时为正值
    Oid resorigtbl;        // 属性所属源表的OID
    AttrNumber resorigcol; // 属性在源表中的编号
    bool resjunk;          // 如果为true,则在输出结果时去除
} TargetEntry;

FROM子句由transformFromClause函数进行处理,最后生成范围表。该函数的主要传参除了结构体ParseState,还包括分析树SelectStmt的fromClause字段。fromClause是List结构,由FROM子句中的表、视图、子查询、函数、连接表达式等构成,由transformFromClauseItem函数进行检查和处理。

Node* transformFromClauseItem(……)
{
    if (IsA(n, RangeVar)) {
        ……
    } else if (IsA(n, RangeSubselect)) {
        ……
    } else if (IsA(n, RangeFunction)) {
        ……
    } else if (IsA(n, RangeTableSample)) {
        ……
    } else if (IsA(n, JoinExpr)) {
        ……
    } else
        ……
    return NULL; 
}

 transformFromClauseItem会根据fromClause字段的每个Node生成一个或多个RangeTblEntry结构,加入ParseState的p_rtable字段指向的链表中,最终生成查询树的rtable字段也会指向该链表。RangeTblEntry结构如下。

typedef struct RangeTblEntry {
    NodeTag type;
RTEKind rtekind; // RTE的类型
……
    Oid relid; // 表的OID
    Oid partitionOid; // 如果是分区表,记录分区表的OID
    bool isContainPartition; // 是否含有分区表
    Oid refSynOid;
    List* partid_list;

    char relkind; // 表的类型
    bool isResultRel;
    TableSampleClause* tablesample; // 对表基于采样进行查询的子句

    bool ispartrel; // 是否为分区表
    bool ignoreResetRelid;
    Query* subquery;       // 子查询语句
    bool security_barrier; // 是否为security_barrier视图的子查询

    JoinType jointype;   // 连接类型
    List* joinaliasvars; // 连接结果中属性的别名

    Node* funcexpr;          // 函数调用的表达式树
    List* funccoltypes;      // 函数返回记录中属性类型的OID列表
    List* funccoltypmods;    // 函数返回记录中属性类型的typmods列表
    List* funccolcollations; // 函数返回记录中属性类型的collation OID列表

    List* values_lists;      // VALUES表达式列表
    List* values_collations; // VALUES属性类型的collation OID列表
    ……
} RangeTblEntry;

处理WHERE子句的入口函数是transformWhereClause,该函数调用transformExpr将分析树SelectStmt下whereClause字段表示的WHERE子句转换为一颗表达式树,然后将ParseState的p_joinlist所指向的链表和从WHERE子句得到的表达式树包装成FromExpr结构,存入查询树的jointree。

typedef struct FromExpr {
    NodeTag type;
    List* fromlist; // 子连接链表
    Node* quals;    // 表达式树
} FromExpr;

 transformStmt函数完成语义分析后会返回查询树。一条SQL语句的每个子句的语义分析结果会保存在Query的对应字段中,比如targetList存储目标属性语义分析结果,rtable存储FROM子句生成的范围表,jointree的quals字段存储WHERE子句语义分析的表达式树。查询树结构体定义如下。

typedef struct Query {
    NodeTag type;

    CmdType commandType; // 命令类型
    QuerySource querySource; // 查询来源
    uint64 queryId; // 查询树的标识符
    bool canSetTag; // 如果是原始查询,则为false;如果是查询重写或者查询规划新增,则为true
    Node* utilityStmt; // 定义游标或者不可优化的查询语句
    int resultRelation; // 结果关系
    bool hasAggs;         // 目标属性或HAVING子句中是否有聚集函数
    bool hasWindowFuncs;  // 目标属性中是否有窗口函数
    bool hasSubLinks;     // 是否有子查询
    bool hasDistinctOn;   // 是否有DISTINCT子句
    bool hasRecursive;    // 公共表达式是否允许递归
    bool hasModifyingCTE; // WITH子句是否包含INSERT/UPDATE/DELETE
    bool hasForUpdate;    // 是否有FOR UPDATE或FOR SHARE子句
    bool hasRowSecurity;  // 重写是否应用行级访问控制
    bool hasSynonyms;     // 范围表是否有同义词
    List* cteList;    // WITH子句,用于公共表达式
    List* rtable;       // 范围表
    FromExpr* jointree; // 连接树,描述FROM和WHERE子句出现的连接
    List* targetList; // 目标属性
    List* starStart; // 对应于ParseState结构体的p_star_start
    List* starEnd; // 对应于ParseState结构体的p_star_end
    List* starOnly; // 对应于ParseState结构体的p_star_only
    List* returningList; // RETURNING子句
    List* groupClause; // GROUP子句
    List* groupingSets; // 分组集
    Node* havingQual; // HAVING子句
    List* windowClause; // WINDOW子句
    List* distinctClause; // DISTINCT子句
    List* sortClause; // ORDER子句
    Node* limitOffset; // OFFSET子句
    Node* limitCount;  // LIMIT子句
    List* rowMarks; // 行标记链表
    Node* setOperations; // 集合操作
    List *constraintDeps;
    HintState* hintState;
    ……
} Query;

 

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值