每个拥有手工创建的初始数据的目录(有些没有)都有一个对应的
.dat 文件,以可编辑的格式包含其初始数据。
每个 .dat 文件包含 Perl 数据结构字面量,这些
字面量只是被 eval 一下就产生一个由哈希引用数组构成的内存数据
结构,每个目录行一个哈希引用。
从 pg_database.dat 中摘取的一个略微修改过的
片段展示了关键特性:
[
# A comment could appear here.
{ oid => '1', oid_symbol => 'Template1DbOid',
descr => 'database\'s default template',
datname => 'template1', encoding => 'ENCODING',
datlocprovider => 'LOCALE_PROVIDER', datistemplate => 't',
datallowconn => 't', dathasloginevt => 'f', datconnlimit => '-1', datfrozenxid => '0',
datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
datctype => 'LC_CTYPE', datlocale => 'DATLOCALE', datacl => '_null_' },
]
需要注意的要点:
文件的整体布局是:一个左方括号、一个或多个代表目录行的花括号组、 一个右方括号。在每个右花括号之后写一个逗号。
在每个目录行内,书写以逗号分隔的
key =>
value 对。允许的
key 是目录各列的名称,外加元数据键
oid、
oid_symbol、
array_type_oid 和 descr。
(oid 和 oid_symbol
的使用在 Section 68.2.2 中描述,
而 array_type_oid 在
Section 68.2.4 中描述。
descr 为对象提供一个描述字符串,它会被插入到
pg_description 或
pg_shdescription(视情况而定)。)
虽然元数据键是可选的,但目录定义的所有列都必须提供,除非目录的
.h 文件为该列指定了默认值。
(在上面的例子中,datdba 字段被省略
是因为 pg_database.h 为它提供了合适的
默认值。)
所有值都必须用单引号括起来。
值内部使用的单引号要用反斜杠转义。
作为数据含义的反斜杠可以(但不必)写成双份;这遵循 Perl 对简单
引号字面量的规则。
注意,作为数据出现的反斜杠会被引导扫描器按与转义字符串常量
相同的规则(见 Section 4.1.2.2)
当作转义处理;例如 \t 会转换为制表符。
如果您确实想让最终值中包含一个反斜杠,需要写四个:
Perl 会去掉两个,留下 \\ 供引导扫描器
看到。
空值用 _null_ 表示。
(注意,没有办法创建一个恰好是该字符串的值。)
注释以 # 开头,并且必须独占一行。
作为其他目录条目 OID 的字段值应当用符号名表示,而不是实际的数字
OID。
(在上面的例子中,dattablespace
就包含这样的引用。)
这在 Section 68.2.3 中描述。
由于哈希是无序数据结构,字段顺序和行的排布在语义上并不重要。
不过,为了保持外观一致,我们制定了一些由格式化脚本
reformat_dat_file.pl 应用的规则:
在每对花括号内,元数据
字段 oid、oid_symbol、
array_type_oid 和 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 文件,看看哪些没有出现。
您还可以使用
duplicate_oids 脚本来检查错误。
(genbki.pl 会为任何没有得到手工分配的行
分配 OID,并且它还会在编译时检测重复的 OID。)
当为不打算立即提交的补丁选择 OID 时,最佳实践是使用一组大致连续的
OID,从范围
8000—9999 中某个随机选择的值开始。
这样可以最小化与其他正在并行开发的补丁发生 OID 冲突的风险。
为了让 8000—9999
范围保持供开发使用,补丁被提交到主 git 仓库之后,其 OID 应当被
重新编号到该范围以下的可用空间中。
通常,这会在每个开发周期接近尾声时进行,把该周期内提交的补丁所
消耗的所有 OID 同时移动。
renumber_oids.pl 脚本可用于这一目的。
如果发现某个未提交的补丁与最近提交的补丁存在 OID 冲突,
renumber_oids.pl 也可用于从这种状况中恢复。
由于存在对补丁分配的 OID 可能重新编号的这一约定,在补丁被包含进 官方发布之前,其分配的 OID 不应被视为稳定。 不过,对象的手工分配 OID 一旦发布我们就不会更改,因为那会带来 各种兼容性问题。
如果 genbki.pl 需要为一个没有手工分配 OID 的
目录条目分配 OID,它会使用 10000—11999
范围内的值。
服务器在 bootstrap 运行开始时把 OID 计数器设为 10000,
因此在 bootstrap 处理期间即时创建的任何对象也会获得这个范围内的
OID。(通常的 OID 分配机制会负责防止任何冲突。)
OID 低于 FirstUnpinnedObjectId(12000)
的对象被视为
“钉住的”(pinned),防止它们被删除。
(存在少量例外,它们被硬编码在
IsPinnedObject() 中。)
initdb 在准备创建非钉住对象时会把
OID 计数器推高到
FirstUnpinnedObjectId。
因此,在 initdb 后续阶段创建的对象
(例如运行 information_schema.sql 脚本时
创建的对象)不会被钉住,而所有
genbki.pl 已知的对象都会被钉住。
在正常数据库操作期间分配的 OID 被约束为 16384 或更高。
这确保了 10000—16383 范围保持空闲,供
genbki.pl 或
initdb 自动分配的 OID 使用。
这些自动分配的 OID 不被视为稳定,在从一个安装到另一个安装时可能
会变化。
原则上,从一个初始目录行到另一个的交叉引用可以只通过在被引用行
的引用字段中写出其预分配的 OID 来完成。
然而,这违反项目政策,因为它容易出错、难以阅读,并且如果新分配的
OID 被重新编号就会破坏。
因此 genbki.pl 提供了改为书写符号引用的
机制。
规则如下:
通过把 BKI_LOOKUP(
附加到某个目录列的定义上,即可在该列中启用符号引用,其中
lookuprule)lookuprule
是被引用目录的名称,例如 pg_proc。
BKI_LOOKUP 可以附加到类型为
Oid、regproc、oidvector
或 Oid[] 的列上;对于后两种类型,它意味着对数组的
每个元素执行查找。
还允许把 BKI_LOOKUP(encoding)
附加到整型列上,以引用字符集编码,这些编码目前没有被表示为目录
OID,但拥有一组 genbki.pl 已知的值。
在某些目录列中,条目允许为零而不是有效的引用。
如果允许,请写
BKI_LOOKUP_OPT 而不是
BKI_LOOKUP。此时您可以为条目
写 0。(如果该列被声明为
regproc,您还可以选择写
- 而不是 0。)
除这一特殊情况外,
BKI_LOOKUP 列中的所有条目都必须是符号
引用。
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
表示。
在以上所有情况中都不提供模式限定;在 bootstrap 期间创建的所有
对象都预期位于 pg_catalog 模式中。
genbki.pl 在运行时会解析所有符号引用,并把
简单的数字 OID 放进输出的 BKI 文件中。
因此引导后端无需处理符号引用。
即使目录没有任何需要查找的初始数据,也值得用
BKI_LOOKUP 或 BKI_LOOKUP_OPT
标记 OID 引用列。
这让 genbki.pl 能够记录系统目录中存在的外键
关系。这些信息被回归测试用于检查不正确的条目。
另请参见宏
DECLARE_FOREIGN_KEY、
DECLARE_FOREIGN_KEY_OPT、
DECLARE_ARRAY_FOREIGN_KEY
和 DECLARE_ARRAY_FOREIGN_KEY_OPT,它们
用于声明对于 BKI_LOOKUP 而言过于复杂的外键
关系(通常是多列外键)。
大多数标量数据类型都应有一个对应的数组类型(即一个元素类型为该
标量类型的标准 varlena 数组类型,并且被该标量类型的
pg_type
条目的 typarray 字段引用)。
genbki.pl 在大多数情况下能够自动生成
数组类型的 pg_type 条目。
要使用这一设施,只需在该标量类型的
pg_type 条目中写一个
array_type_oid
=> 元数据字段,指定
数组类型要使用的 OID。
随后您可以省略 nnnntyparray 字段,
因为它会被自动填入该 OID。
生成的数组类型的名称是标量类型的名称加一个下划线前缀。
数组条目的其他字段从
pg_type.h 中的
BKI_ARRAY_DEFAULT(
注解填充,如果没有这样的注解,则从标量类型复制。
(value)typalign 有一个特例。)
然后,两个条目的 typelem
和 typarray 字段被设置为相互
交叉引用。
以下是更新目录数据文件时执行常见任务的一些最简单方法的 建议。
Add a new column with a default to a catalog:
在头文件中加入该列,并附上一个
BKI_DEFAULT(
注解。
数据文件只需调整:在需要非默认值的现有行中加入该字段。
value)
Add a default value to an existing column that doesn't have
one:
在头文件中加入 BKI_DEFAULT 注解,
然后运行 make reformat-dat-files 以移除现在
冗余的字段条目。
Remove a column, whether it has a default or not:
从头文件中移除该列,然后运行
make
reformat-dat-files 以移除现在无用的字段条目。
Change or remove an existing default value:
您不能简单地修改头文件,因为那会导致当前数据被错误地解释。
先运行 make
expand-dat-files 重写数据文件,显式插入所有默认值,
然后修改或移除
BKI_DEFAULT 注解,再运行
make
reformat-dat-files 再次移除多余的字段。
Ad-hoc bulk editing:
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