支持的版本:当前 (17) / 16 / 15 / 14 / 13
开发版本:devel
不支持的版本:12 / 11

67.2. 系统目录初始数据 #

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

67.2.1. 数据文件格式 #

每个 .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 是目录的列名,加上元数据键 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。如果必须从 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。)

在为预计不会立即提交的补丁选择 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 分配机制负责防止任何冲突。)

对象 ID(OID)低于 FirstUnpinnedObjectId (12000) 的对象被认为是已锁定,防止它们被删除。(存在少量例外情况,这些例外情况被硬编码到 IsPinnedObject() 函数中。)initdb 在准备好创建未锁定的对象时,会强制将 OID 计数器提升到 FirstUnpinnedObjectId。因此,在 initdb 的后续阶段创建的对象,例如在运行 information_schema.sql 脚本时创建的对象,将不会被锁定,而 genbki.pl 所知的所有对象都将被锁定。

在正常数据库操作期间分配的 OID 被限制为 16384 或更高。 这确保了 10000—16383 的范围可用于 genbki.pl 或在 initdb 期间自动分配的 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. 自动创建数组类型 #

大多数标量数据类型应该具有相应的数组类型(即,标准的可变长度数组类型,其元素类型为标量类型,并且由标量类型的 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-files 以从 pg_proc.dat 中删除无用的旧条目。

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

提交更正

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