2024年9月26日: PostgreSQL 17 发布!
支持版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持版本:11

67.2. 系统目录初始数据 #

每个具有手动创建的初始数据(有些没有)的目录都有一个对应的.dat文件,其中包含其初始数据,以可编辑的格式呈现。

67.2.1. 数据文件格式 #

每个.dat文件包含Perl数据结构字面量,这些字面量被简单地求值以生成一个内存中的数据结构,该结构包含一个哈希引用数组,每个数组对应一个目录行。以下是从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是目录的列名,加上元数据键oidoid_symbolarray_type_oiddescr。(oidoid_symbol的使用将在下面的第 67.2.2 节中描述,而array_type_oid将在第 67.2.4 节中描述。 descr提供对象的描述字符串,该字符串将被插入到pg_descriptionpg_shdescription中,具体取决于情况。)虽然元数据键是可选的,但目录的定义列必须全部提供,除非目录的.h文件为该列指定了默认值。(在上面的示例中,datdba字段已被省略,因为pg_database.h为其提供了合适的默认值。)

  • 所有值必须用单引号括起来。在值内使用的单引号用反斜杠转义。作为数据的反斜杠可以,但不必加倍;这遵循Perl对简单引号字面量的规则。请注意,作为数据的反斜杠将被引导程序扫描程序视为转义字符,遵循与转义字符串常量相同的规则(参见第 4.1.2.2 节);例如,\t转换为制表符。如果你实际上想要在最终值中使用反斜杠,你需要写四个:Perl会去掉两个,留下\\让引导程序扫描器看到。

  • 空值用_null_表示。(请注意,没有办法创建一个只是该字符串的值。)

  • 注释以#开头,并且必须在它们自己的行上。

  • 作为其他目录项的OID的字段值应该用符号名称而不是实际的数字OID表示。(在上面的示例中,dattablespace包含这样的引用。)这将在下面的第 67.2.3 节中描述。

  • 由于哈希是无序的数据结构,因此字段顺序和行布局在语义上没有意义。但是,为了保持一致的外观,我们设置了一些由格式化脚本reformat_dat_file.pl应用的规则

    • 在每对花括号内,元数据字段oidoid_symbolarray_type_oiddescr(如果存在)首先出现,按此顺序排列,然后目录自己的字段按其定义顺序出现。

    • 根据需要在字段之间插入换行符,以将行长限制在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()教授如何将数据扩展回完整表示形式。

67.2.2. OID分配 #

出现在初始数据中的目录行可以通过写入oid => nnnn元数据字段来手动分配OID。此外,如果分配了OID,可以通过写入oid_symbol => name元数据字段来创建该OID的C宏。

预加载的目录行必须具有预分配的OID,前提是在其他预加载的行中存在对它们的OID引用。如果行的OID必须从C代码中引用,也需要预分配的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,并且它也会在编译时检测重复的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.plinitdb 自动分配的 OID 保留的。这些自动分配的 OID 被认为是不稳定的,可能会在不同的安装之间发生变化。

67.2.3. OID 引用查找 #

原则上,从一个初始目录行到另一个目录行的交叉引用可以通过在引用字段中写入被引用行的预分配 OID 来完成。然而,这违反了项目策略,因为它容易出错,难以阅读,并且如果新分配的 OID 被重新编号,可能会导致错误。因此,genbki.pl 提供了机制来写入符号引用。规则如下:

  • 通过在目录列的定义中附加 BKI_LOOKUP(lookuprule) 来启用目录列中的符号引用,其中 lookuprule 是被引用目录的名称,例如,pg_procBKI_LOOKUP 可以附加到类型为 OidregprocoidvectorOid[] 的列;在后两种情况下,这意味着对数组的每个元素执行查找。

  • 也可以将 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 条目的 oprleftoprright 字段中出现的方式完全相同。(为一元运算符的省略操作数写 0。)

  • 操作类和操作族的名字只在一个访问方法内是唯一的,所以它们由 access_method_name/object_name 表示。

  • 在这些情况中,都没有为模式限定提供任何规定;在引导期间创建的所有对象都应该位于 pg_catalog 模式中。

genbki.pl 在运行时解析所有符号引用,并将简单的数字 OID 放入生成的 BKI 文件中。因此,引导后端不需要处理符号引用。

即使目录没有需要查找的初始数据,也希望用 BKI_LOOKUPBKI_LOOKUP_OPT 标记 OID 引用列。这允许 genbki.pl 记录系统目录中存在的外部键关系。此信息在回归测试中用于检查错误条目。另请参见宏 DECLARE_FOREIGN_KEYDECLARE_FOREIGN_KEY_OPTDECLARE_ARRAY_FOREIGN_KEYDECLARE_ARRAY_FOREIGN_KEY_OPT,它们用于声明对 BKI_LOOKUP 太复杂的外部键关系(通常是多列外部键)。

67.2.4. 自动创建数组类型 #

大多数标量数据类型应该有一个相应的数组类型(即一个标准的 varlena 数组类型,其元素类型是标量类型,并且由标量类型的 pg_type 条目的 typarray 字段引用)。在大多数情况下,genbki.pl 能够自动生成数组类型的 pg_type 条目。

要使用此功能,只需在标量类型的 pg_type 条目中写入一个 array_type_oid => nnnn 元数据字段,指定用于数组类型的 OID。然后,你可以省略 typarray 字段,因为它将自动使用该 OID 填充。

生成的数组类型的名称是标量类型的名称,前面加上一个下划线。数组条目的其他字段从 pg_type.h 中的 BKI_ARRAY_DEFAULT(value) 注释中填充,或者如果没有,则从标量类型中复制。(typalign 还存在一个特殊情况。)然后,两个条目的 typelemtyparray 字段被设置为相互交叉引用。

67.2.5. 编辑数据文件的秘诀 #

以下是一些关于在更新目录数据文件时执行常见任务的最简单方法的建议。

向目录添加一列,并使用默认值: 在头文件中添加该列,并使用 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 中的两个布尔字段到一个字符字段

  1. pg_proc.h 添加新列,并使用默认值

    +    /* see PROKIND_ categories below */
    +    char        prokind BKI_DEFAULT(f);
    
  2. 根据 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';
    +           }
    
  3. 运行新脚本

    $ cd src/include/catalog
    $ perl  rewrite_dat_with_prokind.pl  pg_proc.dat
    

    此时,pg_proc.dat 具有所有三个列 prokindproisaggproiswindow,尽管它们将只出现在具有非默认值的那些行中。

  4. pg_proc.h 中删除旧列

    -    /* is it an aggregate? */
    -    bool        proisagg BKI_DEFAULT(f);
    -
    -    /* is it a window function? */
    -    bool        proiswindow BKI_DEFAULT(f);
    
  5. 最后,运行 make reformat-dat-filespg_proc.dat 中删除无用的旧条目。

有关用于批量编辑的脚本的更多示例,请参阅附加到此消息的 convert_oid2name.plremove_pg_type_oid_symbols.plhttps://postgresql.ac.cn/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com

提交更正

如果你在文档中发现任何不正确的内容、与你对特定功能的体验不符或需要进一步澄清的内容,请使用 此表格 报告文档问题。