69.1. 系统目录声明规则

一个目录头文件的关键部分是一个C的结构定义,它描述该目录中每一行的布局。这个结构开始于一个CATALOG宏,它对于C编译器而言只不过是typedef struct FormData_catalogname的一个简写。该结构中的每一个域会导致出现一个目录列。域可以用genbki.h中描述的BKI属性宏进行标注,例如可以为域定义默认值或者把域标记为可以为空或者不能为空。CATALOG行也可以用genbki.h中描述的一些其他BKI属性宏标注,用于定义该目录整体的其他属性,例如是否有OID(默认是有的)。

系统目录缓冲代码(以及大部分目录功能代码)假定所有的系统目录元组的定长部分是实际的存在形式,因为它会把这个C结构声明映射到定长部分之上。因此,所有变长域和可以为空的域必须被放置在最后,并且不能够以结构的域的方式访问。例如,如果尝试设置pg_type.typrelid为NULL,当某段代码尝试引用typetup->typrelid(或者更糟糕的是引用typetup->typelem,因为它跟随在typrelid之后)时将会出现失败。这会导致随机错误乃至段错误。

作为对这类错误的一种部分保护,变长或可以为空的域不应该对C编译器可见。通过将它们包裹在#ifdef CATALOG_VARLEN ... #endif(其中CATALOG_VARLEN是一个永不被定义的符号)中可以实现这一点。这能防止C代码不小心尝试访问可能不在那里或者可能在其他某个偏移位置的域。作为一种防止创建不正确行的措施,我们要求所有应该为非空的列在pg_attribute中也被标记为非空。如果目录列是定长的并且前面没有任何可以为空的列,bootstrap代码将自动把它标记为NOT NULL。在这一规则不适用的地方,可以根据需要使用BKI_FORCE_NOT_NULLBKI_FORCE_NULL标注强制正确的标记。但是要注意的是,NOT NULL约束仅在执行器中被强制,而不会针对随机C代码产生的元组进行强制,因此在手工创建或者更新目录行时仍需谨慎。

前端代码不应该包括任何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命令来创建,因为那个命令需要在这些目录中写入信息来描述新的目录。这些目录被称为bootstrap目录,定义一个这样的目录需要一些额外的工作:开发者必须为它在pg_classpg_type的预装载内容中手工准备合适的项,并且后续对该目录结构的更改将会更新那些项(bootstrap目录还需要pg_attribute中的预装载项,但是幸运地是现如今的genbki.pl会处理这些杂务)。如果可能,一定避免将新目录创建为bootstrap目录。