Redrock Postgres 搜索 英文
版本: 11 / 12 / 13 / 14 / 15 / 16 / 17

67.2. 系统目录初始数据 #

67.2.1. 数据文件格式
67.2.2. OID 分配
67.2.3. OID 引用查找
67.2.4. 自动创建数组类型
67.2.5. 编辑数据文件的流程

每个包含任何手动创建的初始数据(有些没有)的目录都有一个对应的 .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 文件以查看哪些没有出现。您还可以使用 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 不被认为是稳定的,且从一个安装可能会更改为另一个安装。

67.2.3. OID 引用查找 #

原则上,可以简单地通过在引用字段中写入引用的行的预分配 OID 来写入从一个初始目录行到另一初始目录行的交叉引用。但是,这违背项目策略,因为它容易出错、难于阅读,而且如果重新编号一个新分配的 OID,它容易遭到破坏。因此,genbki.pl 提供了用于写入符号引用的机制。规则如下

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

  • 也可以将 BKI_LOOKUP(encoding) 附加到整数列来引用字符集编码,这种编码目前不表示为目录 OID,但 genbki.pl 有一组已知的值。

  • 在某些目录列中,允许条目为零,而不是有效引用。如果允许,请写入 BKI_LOOKUP_OPT 而不是 BKI_LOOKUP。然后,可以为一个条目写入 0。(如果声明列为 regproc,可以选择写入 - 而不是 0。)除了这种特殊情况,BKI_LOOKUP 列中的所有条目都必须为符号引用。genbki.pl 会针对无法识别的名称发出警告。

  • 大多数类型的目录对象都通过其名称直接引用。请注意,类型名称必须完全匹配引用的 pg_type 条目的 typname;无法使用任何别名,例如对 int4 使用 integer

  • 函数可以使用其 proname 表示,如果它在 pg_proc.dat 条目中是唯一的(这类似于 regproc 输入)。否则,将其编写为 proname(argtypename,argtypename,...),类似于 regprocedure。参数类型名称必须按照 pg_proc.dat 条目中的 proargtypes 字段中拼写的那样。不要插入任何空格。

  • 运算符使用 oprname(lefttype,righttype) 表示,将类型名称按照其在 pg_operator.dat 条目中的 oprleftoprright 字段中显示的那样编写。(对于一元运算符的省略操作数,编写 0)。

  • opclasses 和 opfamilies 的名称在访问方法内是唯一的,因此它们使用 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。

生成数组类型的名称是在标量类型名称前加一个下划线。数组条目的其他字段由 BKI_ARRAY_DEFAULT(value) 批注从 pg_type.h 填写,如果没有批注,则从标量类型复制。(对于 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://postgresql.ac.cn/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com