目录头文件中的关键部分是一个 C 结构定义,该定义描述了目录每行的布局。这从一个 CATALOG
宏开始,对于 C 编译器来说,这只是 typedef struct FormData_
的简写。结构中的每个字段都会生成一个目录列。可以使用 catalogname
genbki.h
中所述的 BKI 属性宏来注释字段,例如为字段定义一个默认值或将其标记为可为空或不可为空。 CATALOG
行也可以使用 genbki.h
中所述的一些其他 BKI 属性宏进行注释,以定义整个目录的其他属性,例如它是否是共享关系。
系统目录缓存代码(以及大多数一般的目录映射代码)假定所有系统目录元组的固定长度部分实际上都存在,因为它将此 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
如今已经负责了这项任务。)尽可能避免将新建编录设为引导编录。