目录头文件的关键部分是一个描述目录每一行布局的 C 结构定义。
它以一个 CATALOG 宏开始,就 C 编译器而言它只是
typedef struct
FormData_ 的
速记。结构中的每个字段都产生一个目录列。
字段可以使用 catalognamegenbki.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 会处理这项工作。)
如果可能,应避免让新目录成为引导目录。