每个含有手工创建初始数据的目录(有些目录没有)都有一个对应的 .dat 文件,其中以可编辑格式保存该目录的初始数据。
每个 .dat 文件都包含 Perl 数据结构字面量,只需直接用 eval 求值,就会得到一个由哈希引用数组组成的内存数据结构,其中每个数组元素对应一行目录数据。下面摘自 pg_database.dat 的一小段、经过轻微修改的内容,可以展示其关键特性:
[
# A comment could appear here.
{ oid => '1', oid_symbol => 'TemplateDbOid',
descr => 'database\'s default template',
datname => 'template1', datdba => 'PGUID', encoding => 'ENCODING',
datcollate => 'LC_COLLATE', datctype => 'LC_CTYPE', datistemplate => 't',
datallowconn => 't', datconnlimit => '-1', datlastsysoid => '0',
datfrozenxid => '0', datminmxid => '1', dattablespace => '1663',
datacl => '_null_' },
]
需要注意的几点:
整个文件的布局是:一个左方括号、一个或多个花括号集合(每个集合代表一行目录数据),最后是一个右方括号。每个右花括号后面都要写一个逗号。
在每个目录行中,写出以逗号分隔的 key => value 对。允许的 key 包括目录列名,以及元数据键 oid、oid_symbol 和 descr。(oid 和 oid_symbol 的用法在下文 Section 70.2.2 中说明。descr 提供对象的描述字符串,它会按情况插入 pg_description 或 pg_shdescription。)元数据键是可选的,但目录定义的列必须全部提供,除非目录的 .h 文件为该列指定了默认值。
所有值都必须用单引号括起来。值内部出现的单引号要用反斜线转义。作为数据的反斜线可以写成双反斜线,但也不必;这遵循 Perl 对简单单引号字面量的规则。注意,作为数据出现的反斜线也会按照转义字符串常量相同的规则被引导扫描器当作转义处理(见 Section 4.1.2.2);例如 \t 会转换为一个制表符。如果你确实希望最终值里包含一个反斜线,就需要写四个:Perl 会先去掉两个,留给引导扫描器看到的是 \\。
NULL 值用 _null_ 表示。(注意,没有办法创建一个值恰好等于这个字符串。)
注释以前缀 # 表示,并且必须单独成行。
为提高可读性,如果字段值是其他目录条目的 OID,可以用名字代替数值 OID 来表示。下文 Section 70.2.3 将对此作出说明。
由于哈希是无序的数据结构,字段顺序和行布局没有语义意义。不过,为保持一致的外观,我们设置了若干规则,由格式化脚本来应用这些规则。该脚本是reformat_dat_file.pl:
在每对花括号内,元数据字段 oid、oid_symbol 和 descr(如果存在)按此顺序排在最前,然后是按定义顺序排列的目录自身字段。
如有可能,会根据需要在字段之间插入换行,以便把行长限制在 80 个字符以内。元数据字段和普通字段之间也会插入一次换行。
如果目录的 .h 文件为某列指定了默认值,而某条数据又使用了同样的值,reformat_dat_file.pl 就会把该字段从数据文件中省略掉,以保持数据表示紧凑。
reformat_dat_file.pl 会原样保留空行和注释行。
建议在提交目录数据补丁前运行reformat_dat_file.pl。为方便起见,可以直接切换到src/include/catalog/并运行make reformat-dat-files。
如果你想新增一种缩小数据表示的方法,就必须在 reformat_dat_file.pl 中实现它,并且还要让 Catalog::ParseData() 知道如何把数据重新展开为完整表示。
出现在初始数据中的目录行,可以通过写一个 oid => 元数据字段来手工指定 OID。此外,如果已经指定了 OID,还可以通过写一个 nnnnoid_symbol => 元数据字段,为该 OID 创建一个 C 宏。name
如果其他预装载行通过 OID 引用某个预装载目录行,该行就必须具有预先分配的 OID。如果 C 代码需要引用该行的 OID,也必须预先分配。若这两种情况均不适用,可以省略 oid 元数据字段,此时引导代码会自动分配 OID,或者在没有 OID 的目录中将其保留为零。实践中,即使只有部分行实际被交叉引用,我们通常也会为某个目录中的所有预装载行预先分配 OID,或者一个都不预先分配。
在 C 代码里直接写任何 OID 的实际数值,都被认为是很不好的做法;应始终改用宏。对 pg_proc OID 的直接引用十分常见,因此有一个专门机制可以自动创建所需宏;见 src/backend/utils/Gen_fmgrtab.pl。类似地,pg_type OID 也有一种自动创建宏的方法,只是出于历史原因,做法并不完全相同。因此,这两个目录中并不需要 oid_symbol 项。同样,系统目录和索引的 pg_class OID 宏也是自动建立的。对于其他所有系统目录,你必须通过 oid_symbol 项手工指定所需的宏。
要为新的预装载行找到可用 OID,请运行脚本 src/include/catalog/unused_oids。它会输出未使用 OID 的范围(包含两个端点,例如输出行 “45-900” 表示 OID 45 到 900 尚未分配)。目前,OID 1-9999 保留供手动分配;unused_oids 脚本只是检查目录头文件和 .dat 文件,找出哪些 OID 没有出现。还可以使用 duplicate_oids 脚本检查错误。(genbki.pl 也会在编译时检测重复 OID。)
引导运行开始时,OID 计数器从 10000 开始。如果目录行所在的表需要 OID,而又未通过 oid 字段预先分配 OID,那么该行将获得一个不小于 10000 的 OID。
从一个初始目录行到另一个初始目录行的交叉引用,可以直接写出被引用行预先分配的 OID。但这容易出错,也难以理解,因此对于经常被引用的目录,genbki.pl 提供了使用符号引用的机制。目前,访问方法、函数、操作符、操作符类、操作符族和类型的引用可以使用此机制。规则如下:
要在某个目录列中启用符号引用,可以在列定义后附加 BKI_LOOKUP(,其中 lookuprule)lookuprule 为 pg_am、pg_proc、pg_operator、pg_opclass、pg_opfamily 或 pg_type。BKI_LOOKUP 可以附加在类型为 Oid、regproc、oidvector 或 Oid[] 的列上;在后两种情况下,它表示对数组中的每个元素都执行一次查找。
在这样的列中,除用 0 表示 InvalidOid 外,所有条目都必须使用符号形式。(如果该列声明为 regproc,还可以选择用 - 代替 0。)genbki.pl 会对无法识别的名字发出警告。
访问方法直接用名称表示,类型也是如此。类型名必须与被引用的 pg_type 条目中的 typname 完全一致;不能使用诸如用 integer 代表 int4 这样的别名。
如果函数的 proname 在 pg_proc.dat 条目中唯一,那么可以用它来表示函数(这和 regproc 输入的行为类似)。否则,应把它写成 proname(argtypename,argtypename,...) 的形式,类似 regprocedure。参数类型名的拼写必须与该 pg_proc.dat 条目中 proargtypes 字段里的写法完全一致。不要插入任何空格。
操作符写成 oprname(lefttype,righttype) 的形式,其中类型名必须与 pg_operator.dat 条目中 oprleft 和 oprright 字段里的写法完全一致。(对于一元操作符中省略的操作数,写 0。)
操作符类和操作符族的名字只在某个访问方法内部唯一,因此它们写成 access_method_name/object_name 的形式。
在以上所有情形中,都不支持模式限定;引导期间创建的所有对象都应位于 pg_catalog 模式中。
genbki.pl 在运行时会解析所有符号引用,并在输出的 BKI 文件中写入简单的数字 OID。因此,引导后端无需处理符号引用。
下面给出一些建议,说明在更新目录数据文件时,执行常见任务最简便的方法。
向目录中增加一个带默认值的新列:. 在头文件中加入该列,并加上 BKI_DEFAULT( 标注。数据文件通常只需在那些需要非默认值的现有行中补上该字段。value)
为现有列增加一个原本没有的默认值:. 在头文件中加上 BKI_DEFAULT 标注,然后运行 make reformat-dat-files,移除现在已经冗余的字段项。
移除一列,不论它是否有默认值:. 从头文件中删掉该列,然后运行 make reformat-dat-files,移除现在已经无用的字段项。
修改或移除现有默认值:. 不能只改头文件,因为那样会导致当前数据被错误解释。首先运行 make expand-dat-files,把所有默认值都显式写入数据文件;然后修改或移除 BKI_DEFAULT 标注;再运行 make reformat-dat-files,重新去掉多余字段。
临时性的批量编辑:. reformat_dat_file.pl可以经过调整来执行多种批量更改。请查找其中说明可以插入一次性代码的位置的块注释。在下面的示例中,我们会将pg_proc中的两个布尔字段合并为一个 char 字段:
在 pg_proc.h 中加入一个带默认值的新列:
+ /* see PROKIND_ categories below */ + char prokind BKI_DEFAULT(f);
基于 reformat_dat_file.pl 新建一个脚本,在处理过程中插入合适的值:
- # At this point we have the full row in memory as a hash
- # and can do any operations we want. As written, it only
- # removes default values, but this script can be adapted to
- # do one-off bulk-editing.
+ # One-off change to migrate to prokind
+ # Default has already been filled in by now, so change to other
+ # values as appropriate
+ if ($values{proisagg} eq 't')
+ {
+ $values{prokind} = 'a';
+ }
+ elsif ($values{proiswindow} eq 't')
+ {
+ $values{prokind} = 'w';
+ }
运行这个新脚本:
$ cd src/include/catalog $ perl rewrite_dat_with_prokind.pl pg_proc.dat
此时,pg_proc.dat 会同时具有 prokind、proisagg 和 proiswindow 这三列,不过它们只会出现在取值非默认的那些行里。
从 pg_proc.h 中移除旧列:
- /* is it an aggregate? */ - bool proisagg BKI_DEFAULT(f); - - /* is it a window function? */ - bool proiswindow BKI_DEFAULT(f);
最后,运行 make reformat-dat-files,从 pg_proc.dat 中移除无用的旧项。
更多批量编辑脚本示例,请参见convert_oid2name.pl和remove_pg_type_oid_symbols.pl,它们附在以下消息中:https://www.postgresql.org/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com