每个有手工创建的初始数据(有些没有)的目录都有一个相应的.dat
文件,其中以可编辑的格式包含着该目录的初始数据。
每个.dat
文件含有Perl数据结构文本,它可以简单地通过eval产生由一个哈希引用数组构成的内存数据结构,每个目录行一个。从pg_database.dat
摘出的经过略微修改的一小部分可以展示关键特性:
[ # A comment could appear here. { oid => '1', oid_symbol => 'TemplateDbOid', descr => 'database\'s default template', datname => 'template1', datdba => 'PGUID', encoding => 'ENCODING', datcollate => 'LC_COLLATE', datctype => 'LC_CTYPE', datistemplate => 't', datallowconn => 't', datconnlimit => '-1', datlastsysoid => '0', datfrozenxid => '0', datminmxid => '1', dattablespace => '1663', datacl => '_null_' }, ]
需要注意的点:
总体的文件布局是:开方括号,一个或者多个花括号集合(每一个表示一个目录行),闭方括号。在每一个闭花括号之后写一个逗号。
在每个目录行内,写成逗号分隔的key
=>
value
对。允许的key
是该目录的列名,外加上元数据键oid
、oid_symbol
以及descr
(oid
和oid_symbol
的使用在下文的第 69.2.2 节中描述。descr
为该对象提供一个描述字符串,它将被插入到pg_description
或pg_shdescription
中)。虽然元数据键是可选的,但是目录中定义的列必须全部被提供,除非目录的.h
文件为该列指定了默认值。
所有的值都必须被放在单引号中。用反斜线可以转义值中用到的单引号。作为数据的反斜线可以(但是不必)被双写,这遵循的是Perl对简单引用文本的规则。注意,作为数据出现的反斜线将被bootstrap扫描器根据转义字符串常量的相同规则(见第 4.1.2.2 节)当作转义处理。例如\t
转换为一个制表符。如果在最终值中确实想要一个反斜线,则需要写成四个:Perl会剥离掉两个,留下\\
给bootstrap扫描器。
空值被表示为_null_
(注意没有办法创建就是该字符串的值)。
注释以#
开头,并且必须位于它们自己的行上。
为了帮助可读性,是其他目录项OID的域值可以用名称而不是数字的OID表示。这会在下文的第 69.2.3 节中描述。
因为哈希是无序的数据结构,域顺序和行布局并不重要。不过,为了维持一种一致的外貌,我们设定了一些规则,它们由格式化脚本reformat_dat_file.pl
实施:
在每一对花括号内,元数据域oid
、oid_symbol
和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。如果行的OID必须被C代码引用,也需要预分配的OID。如果两种情况都不符合,则oid
元数据域可以被省略,在这种情况下bootstrap代码会自动分配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)。
在bootstrap运行开始时,OID计数器从10000开始。如果表中的一个目录行要求OID但没有通过oid
域预分配OID,那么它将得到一个大于等于10000的OID。
从一个初始目录行到另一个初始目录行的交叉引用只需要在引用行中写上被引用行的预分配OID就可以实现。但这种方式容易出错并且难于理解,因此对于频繁引用的目录,genbki.pl
提供了机制支持符号化的引用。当前这些机制可以用来引用访问方法、函数、操作符、操作符类、操作符族以及类型。规则如下:
通过对特定的目录列定义附加BKI_LOOKUP(
来开启对符号化引用的使用,其中lookuprule
)lookuprule
是pg_am
、pg_proc
、pg_operator
、pg_opclass
、pg_opfamily
或者pg_type
。BKI_LOOKUP
可以被附加到类型为oid
、regproc
、oidvector
或者Oid[]
的列上,在后两种情况中它意味着在数组的每个元素上执行查找。
在这样的一个列中,所有的项必须使用符号化格式,不过写成0
(表示InvalidOid)除外(如果列被声明为regproc
,可以选择用-
代替0
)。对于无法识别的名称,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文件中。因此不需要bootstrap后端处理符号化引用。
在更新目录数据文件时,对于执行常用任务的简便方法,这里有一些建议。
向一个目录增加一个带有默认值的新列:.
用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
中的两个boolean域联合成一个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
中移除无用的旧项。
用于批量编辑的脚本的更多例子,请参考这个消息https://www.postgresql.org/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com的附件convert_oid2name.pl
和remove_pg_type_oid_symbols.pl
。