目录
PostgreSQL使用许多不同的系统目录来跟踪数据库对象(例如表和函数)的存在及其属性。系统目录与普通用户表在物理上没有区别,但后端 C 代码知道每个目录的结构和属性,并且能够在较低层次上直接操作它。因此,例如,试图在运行中修改某个目录的结构并不可取;那会破坏 C 代码中关于目录行布局方式的既有假设。不过,目录的结构可能会在主版本之间发生变化。
目录的结构声明在源码树 src/include/catalog/ 目录中的、采用特殊格式的 C 头文件里。每个目录都有一个以该目录命名的头文件(例如,pg_class 对应 pg_class.h),其中定义了该目录具有的列集合,以及 OID 等其他一些基本属性。定义目录结构的其他关键文件包括 indexing.h,它定义所有系统目录上的索引,以及 toasting.h,它为需要 TOAST 表的目录定义 TOAST 表。
许多目录都有初始数据,这些数据必须在 initdb 的 “引导” 阶段装入其中,才能让系统达到可以执行 SQL 命令的程度。(例如,pg_class.h 必须包含它自身的一条记录,以及每个其他系统目录和索引的一条记录。)这些初始数据以可编辑形式保存在同样位于 src/include/catalog/ 目录中的数据文件里。例如,pg_proc.dat 描述了必须插入到 pg_proc 目录中的所有初始行。
为了创建目录文件并将这些初始数据装入其中,以引导模式运行的后端会读取一个包含命令和初始数据的 BKI(Backend Interface,后端接口)文件。在构建 PostgreSQL 发行版时,会由名为 genbki.pl 的 Perl 脚本根据上述头文件和数据文件生成该模式所使用的 postgres.bki 文件。尽管 postgres.bki 针对特定的 PostgreSQL 发行版,但它与平台无关,并会安装到安装树的 share 子目录中。
genbki.pl 还会为每个目录生成一个派生头文件,例如为 pg_class 目录生成 pg_class_d.h。该文件包含自动生成的宏定义,也可能包含其他宏、枚举声明等,对于读取特定目录的客户端 C 代码会很有用。
大多数 Postgres 开发者并不需要直接关心 BKI 文件,但后端中几乎任何稍有分量的功能新增,都需要修改目录头文件和/或初始数据文件。本章余下部分将提供一些相关信息,并且为求完整,也会描述 BKI 文件格式。
目录头文件的关键部分是一个 C 结构定义,用于描述目录中每一行的布局。它以 CATALOG 宏开头;对 C 编译器而言,这只是 typedef struct FormData_ 的简写。结构中的每个字段对应一个目录列。可以使用 catalognamegenbki.h 中描述的 BKI 属性宏来标注字段,例如定义字段的默认值,或者标记其可空或不可空。还可以使用 genbki.h 中描述的其他一些 BKI 属性宏来标注 CATALOG 行,定义整个目录的其他属性,例如它是否具有 OID(默认具有)。
系统目录缓存代码(以及一般而言大多数操作目录的代码)都假定所有系统目录元组的定长部分确实存在,因为它会把这个 C 结构体声明映射到这些部分上。因此,所有变长字段和可空字段都必须放在末尾,并且不能作为结构体字段来访问。例如,如果你试图把 pg_type.typrelid 设为 NULL,那么当某段代码尝试引用 typetup->typrelid 时就会失败;更糟的是,也可能在引用 typetup->typelem 时失败,因为后者紧跟在 typrelid 之后。这会导致随机错误,甚至段错误。
为了在一定程度上防范这类错误,变长字段或可空字段不应该直接暴露给 C 编译器。实现方法是把它们包在 #ifdef CATALOG_VARLEN ... #endif 中(其中 CATALOG_VARLEN 是一个永远不会被定义的符号)。这样可以防止 C 代码不小心去访问那些可能不存在、或位于其他偏移位置的字段。作为防止创建错误行的另一层保护,我们要求所有本应不可空的列都在 pg_attribute 中被标记出来。如果目录列是定宽的,且其前面没有任何可空列,引导代码会自动把它标记为 NOT NULL。在这一规则不足以覆盖的地方,可以按需使用 BKI_FORCE_NOT_NULL 和 BKI_FORCE_NULL 标注来强制正确的标记。
前端代码不应该包含任何 pg_xxx.h 目录头文件,因为这些文件可能含有在后端之外无法编译的 C 代码。(通常,这是因为这些文件也包含了 src/backend/catalog/ 中文件里函数的声明。)前端代码应改为包含相应生成的 pg_xxx_d.h 头文件,其中会带有 OID #define 以及其他任何可能对客户端有用的数据。如果你希望目录头文件中的宏或其他代码对前端代码可见,可以在那一部分外围加上 #ifdef EXPOSE_TO_CLIENT_CODE ... #endif,以指示 genbki.pl 把该部分复制到 pg_xxx_d.h 头文件中。
有少数目录基础到这样的程度,以致它们甚至不能通过大多数目录所使用的 BKI create 命令来创建,因为该命令需要向这些目录中写入信息来描述新目录。这些目录称为引导目录,而定义这样的目录需要额外做很多工作:你必须在 pg_class 和 pg_type 的预装载内容中手工为它们准备合适的条目,并且当该目录的结构后续发生变化时,还需要更新这些条目。(引导目录在 pg_attribute 中也需要预装载条目,不过幸运的是,如今 genbki.pl 会处理这件事。)如果可能的话,应尽量避免让新目录成为引导目录。