每个包含任何手动创建的初始数据(有些没有)的目录都有一个相应的 .dat
文件,其中包含可编辑格式的初始数据。
每个 .dat
文件都包含 Perl 数据结构字面量,只需对它们进行 eval 即可生成一个内存数据结构,该结构由哈希引用数组组成,每个引用对应一行目录。
[ # 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', datconnlimit => '-1', datfrozenxid => '0', datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE', datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' }, ]
需要留意的要点
整体文件布局为:左方括号、一个或多个大括号,每个大括号代表一行目录、右方括号。在每个右方大括号后写一个逗号。
在每个目录行内,编写以逗号分隔的 key
=>
value
对。允许的 key
是目录列的名称,加上元数据键 oid
、oid_symbol
、array_type_oid
和 descr
。(oid
和 oid_symbol
的使用在 第 75.2.2 节 中进行了描述,而 array_type_oid
在 第 75.2.4 节 中进行了描述。 descr
为对象提供描述字符串,该字符串将被适当地插入到 pg_description
或 pg_shdescription
中。)虽然元数据键是可选的,但必须提供目录的已定义列,除非目录的 .h
文件为该列指定了默认值。(在上面的示例中,datdba
字段已省略,因为 pg_database.h
为其提供了合适的默认值。)
所有值都必须用单引号引起来。使用反斜杠转义值中使用的单引号。作为数据的反斜杠可以加倍,但不必加倍;这遵循 Perl 的简单引号文本的规则。请注意,作为数据出现的反斜杠将被引导程序扫描器视为转义符,其规则与转义字符串常量相同(请参阅 第 4.1.2.2 节);例如 \t
转换为制表符。如果您实际上想要在最终值中使用反斜杠,则需要编写四个反斜杠:Perl 去掉两个,留下 \\
供引导程序扫描器查看。
空值由 _null_
表示。(请注意,没有办法创建仅为该字符串的值。)
注释以 #
为前缀,并且必须在自己的行上。
其他目录项的 OID 的字段值应由符号名称表示,而不是实际数字 OID。(在上面的示例中,dattablespace
包含此类引用。)这将在下面的 第 75.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,则可以通过编写 nnnn
oid_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 时,最佳做法是使用一组或多或少连续的 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 范围内的值。服务器的 OID 计数器在引导运行开始时设置为 10000,以便在引导处理期间动态创建的任何对象也接收此范围内的 OID。(通常的 OID 分配机制负责防止任何冲突。)
OID 低于 FirstUnpinnedObjectId
(12000)的对象被视为 “已固定”,防止它们被删除。(有少数例外,这些例外已硬编码到 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
。
如果函数在其 pg_proc.dat
条目中是唯一的,则可以用其 proname
表示(这类似于 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
在运行时解析所有符号引用,并将简单的数字 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。然后,您可以省略 nnnn
typarray
字段,因为它将自动填充该 OID。
生成的数组类型的名称是标量类型的名称,前面加上下划线。数组条目的其他字段从 pg_type.h
中的 BKI_ARRAY_DEFAULT(
注释中填充,如果没有注释,则从标量类型中复制。(value
)typalign
还有一个特例。)然后,两个条目的 typelem
和 typarray
字段设置为相互交叉引用。
以下是一些关于在更新目录数据文件时执行常见任务的最简单方法的建议。
向目录中添加带有默认值的新列: 使用 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
中的两个布尔字段到一个字符字段中
将新列(带有默认值)添加到 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
:http://www.postgresql.org/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com