数据定义语句 (也就是之前我提到的非可优化语句) 是一类用于定义数据模式、函数等的功能性语句。不同于元组增删査改的操作,其处理方式是为每一种类型的描述语句调用相应的处理函数。
数据定义语句的执行流程最终会进入到 ProcessUtility 处理器,然后执行语句对应的不同处理过程。由于数据定义语句的种类很多,因此整个处理过程中的数据结构和方式种类繁冗、复杂,但流程相对简单、固定。这里我们以 Create table 为例说明数据定义语句的具体处理过程。
由于 ProcessUtility 需要处理所有类型的数据定义语句,因此 ProcessUtility 通过判断数据结构中 NodeTag 字段的值来区分各种不同节点,并引导执行流程进人相应的处理函数。
相同类别的语句处理过程涉及内容相近,实现思想和主要过程相似。例如,事务类处理主要是对于当前事务的状态的判断和转换;游标类处理的主要思想是首次将执行一个査询计划树(Plantree), 将结果缓存在 Portal 指向的特殊结构中,然后按照要求获取元组数据;表、属性管理类主要涉及权限管理、修改相应系统表以及关系表的存储类别操作等。由于数据定义语句的种类多达上百种,我们下面将以一个创建表的例子来介绍数据定义语句的执行流程。
针对各种不同的査询树,査询编译器在执行处理前会做一些额外的处理对査询树进行分析、处理与转换。创建表的语句会用函数 transformCreateStmt 进行査询树的处理。这些处理过程可能会在当前操作之前和之后增加一些新的操作(例如在创建表的操作之前增加创建 serial 序列表操作、之后增加创建触发器用于外键约束操作等),也可能会执行对数据结构的处理操作(例如将 CreateStmt 节点 tableElts 字段中 CONST_CHECK 类型的 Constraint 节点转存到 CreateStmt 的 ccmstraints 链表中等)。由于这些处理过程会产生一些新的操作,因此最终会生成一个由多个操作构成的链表。因此,执行过程需要依次扫描该链表,为每一个原子操作调用相应的处理函数。
例 创建一个名为 course 的数据表,此表有三个属性:编号(no, 自增属性〉、姓名
(name) 非空、学分(credit) 非负。其中,包含了一个约束定义,主键被定义为编号(no)。对应
的 SQL 语句如 T:
- CREATE TABLE course (
- no SERIAL,
- name VARCHAR,
- credit INT,
- CONSTRAINT con1 CHECK(credit > = 0 AND name <> ''),
- PRIMARY KEY(no)
- );
系统首先会对査询语句进行词法和语法分析,将査询语句构造为査询树的链表。然后,针对链
表中的每一个査询树进行如下的处理过程(下例仅有一个 T_CreateStmt 类型的査询树):
下面给出了上述査询语句执行时的主要函数调用流程。
- pg_parse_query
- |
- v
- pg_analyze_and_rewrite
- |
- v
- PortalStart -> ChoosePortalStrategy
- |
- v
- PortalRun -> PortalRunMulti -> PortalRunUtility -> ProcessUtility -> standard_ProcessUtility -> ProcessUtilitySlow
- |
- v
- PortalDrop
在上面的例子中,査询编译器会生成一个仅包含一个 T_CreateStmt 类型节点的査询树链表,因此对应的 Portal 的 stmts 字段中也只包含一个 T_CreateStmt 类型节点。ChoosePortalStrategy 函数根据 stmts 字段值选择策略时会选择 PORTAL_MULTI_QUERY 策略。在接下来的 PortalRun 函数中将会调用 PortalRunMuti 来执行 PORTAL_MULTI_QUERY 策略,将会把处理流程引导到 ProcessUtility 中。ProcessUtility 将首先调用函数 transformCreateStmt 对 T_CreateStmt 节点进行转换处理,流程如下所示。该过程会做如下转换:
- ProcessUtilitySlow:
- case T_CreateStmt:
- case T_CreateForeignTableStmt:
- transformCreateStmt()
- |
- v
- foreach(l, stmts){
- if (IsA(stmt, CreateStmt)) or IsA(stmt, CreateForeignTableStmt)
- ...
- DefineRelation
- ...
- else
- ProcessUtility
- ......
- }
创建 SERIAL 表(T_CreateSeqStmt 节点)的操作会被放在 stmts 链表中 T_CreateStmt 节点之前的位置,创建唯一约束索引(T_IndexStmt 节点)的操作被放置在 T_CreateStmt 节点
之后。最后还会将单独定义或与属性同时定义的 CONSTR_CHECK 类型约束全部转移到 T_CreateStmt 节点的 constraints 字段所指向的链表中。
最后,transformCreateStmt 将原有的 T_CreateStmt 操作转换为一个操作序列: 依次为 T_CreateSeqStmt (创建序列表)、T_CreateStmt (创建数据表)、T_IndexStmt (创建唯一约束索引)。
- CREATE SERIAL TABLE course_no_seq;--用于产生自增序列
- CREATE TABLE course (
- noint40id DEFAULT nextval (),
- nameVARCHAR,
- creditINT,
- CONSTRAINT coni CHECK (credit >=0 AND name <> "),
- CREATE INDEX course_pkey;--用于唯一检査
之后 ProcessUtility 将逐个对序列中的操作进行处理。对 T_CreateStmt 操作将会调用 DefineRelation 进行数据表的创建,而其他节点则会通过递归调用 ProcessUtility 进人相应的处理过程。下面展示了 T_CreateStmt 操作的处理过程
创建表的过程由函数 DefineRelation 完成,其流程如下:
- DefineRelation
- ->
- Permission check
- transformRelOptions()
- heap_reloptions()
- MergeAttributes()
- BuildDescForRelation()
- interpretOidsOption()
- CONSTR_DEFAULT or CONSTR_CHECK
- heap_create_with_catalog()
- StoreCatalogInheritance()
- AddRelationNewConstraints()
- ObjectAddressSet()
表创建函数的主要功能是由 heap_create_with_catalog 完成的,之前的各种操作主要是构造 heap_create_with_catalog 所需要的参数。例如,WITH 子句处理主要完成其中存储相关参数的处理,以便存人 pg_class 系统表的 reloptions 字段中;BuildDescForRelation 主要处理表定义中属性名、类型、非空约束以便构造 pg_attribute 系统表相关内容。
heap_create_with_catalog 函数首先会根据要创建表的属性描述信息、表的名称、命名空间等使用 heap_create 创建一个 RelationData 结构并放人 RelCache,并根据这些信息通过调用 RelalionCreateStorage 函数创建物理文件。然后调用 AddNewRelationType, 向 pg_type 中增加一条关于该表的记录。AddNewRelationTuple 则会将表的相关信息插人 pg_class 系统表中,而 AddNewAttributeTuples 将表的每个属性记录到 pg_attribute 系统表中。最后还需要通过调用 StoreConstraints 将约束和默认值分别存储到 pg_constraint 和 pg_attrdef 中。
从创建表的例子可以看到,功能处理器(ProcessUtility) 本身只作为入口选择函数,它会根据
输人的节点类型调用相应的处理过程。除了创建表的处理过程之外,下面列出了几种常见的
输人节点类型,并给出了其对应处理函数以及其功能简介。
注:本文参考了《Postgresql 数据库内核分析》一书。
来源: https://www.cnblogs.com/flying-tiger/p/7080926.html