跳到主要内容

数据类型扩展与大对象

数据类型扩展与大对象​

本文件包含模块:isn(国际标准编号 ISBN/ISSN 等)、lo(大对象)、ltree(层级路径)、pageinspect(页面检查)。

isn​

isn模块为下列国际产品编号标准提供数据类型:EAN13、UPC、ISBN(图书)、ISMN(音 乐)以及 ISSN(期刊)。在输入时会按照一个硬编码的前缀列表对输入进行验证,这个前缀的列表也被用来在输出时连接号码。因为新的前缀总是不时地出现,这个前缀列表可能会过时。这个模块的一个未来版本有希望得到一个来自于一个或多个表的前缀列表,这样用户可以根据需要来方便地更新前缀列表。不过,在当前该列表只能通过修改源代码并且重新编译来更新。另外一种方案是,在这个模块的未来版本中可能会直接移除掉前缀验证和连接支持。该模块被认为是"trusted",也就是说,它可以由对当前数据库具有CREATE权限的非超级用户安装。

数据类型​

表 D.11展示了isn模块提供的数据类型。

表 D.11. isn 数据类型

--------------------------------- ---------------------------------------
数据类型 描述
EAN13 欧洲文章号,总是以 EAN13 格式显示
ISBN13 国际标准图书号,以新的 EAN13 格式显示
ISMN13 国际标准音乐号,以新的 EAN13 格式显示
ISSN13 国际标准期刊号,以新的 EAN13 格式显示
ISBN 国际标准图书号,以旧的短格式显示
ISMN 国际标准音乐号,以旧的短格式显示
ISSN 国际标准期刊号,以旧的短格式显示
UPC 通用产品代码
--------------------------------- ---------------------------------------

一些注记:

1. ISBN13、ISMN13、ISSN13 号码都是 EAN13 号码。

2. EAN13 号码不总是 ISBN13、ISMN13 或 ISSN13(有些是)。

3. 一些 ISBN13 号码能够作为 ISBN 显示。

4. 一些 ISMN13 号码能够作为 ISMN 显示。

5. 一些 ISSN13 号码能够作为 ISSN 显示。

6. UPC 号码是 EAN13 号码的一个子集(它们基本上是去掉了第一个0位的 EAN13)。

7. 所有 UPC、ISBN、ISMN 以及 ISSN 号码可以被表示为 EAN13 号码。

在内部,所有这些类型使用同一种表达(一个64位整数)并且所有内部表达是可以互换的。多种类型被提供来控制显示格式化并且对假定为表示一种特定类型号码的输入进行更严格的合法性检查。

在可能时,ISBN、ISMN和ISSN类型将显示号码的短版本(ISxN 10),并且在无法适应短版本时显示号码的 ISxN 13 格式。EAN13、ISBN13、ISMN13和ISSN13类型总是显示长版本的 ISxN(EAN13)。

造型​

isn模块提供了下列类型之间的造型:

• ISBN13 <=> EAN13
• ISMN13 <=> EAN13
• ISSN13 <=> EAN13
• ISBN <=> EAN13
• ISMN <=> EAN13
• ISSN <=> EAN13
• UPC <=> EAN13
• ISBN <=> ISBN13
• ISMN <=> ISMN13
• ISSN <=> ISSN13

当从EAN13造型为另一种类型时,会有对该值是否在另一种类型的域中的运行时检查,如果不在则抛出一个错误。其他的造型则是简单地重新贴个标签,因而总是会成功。

函数和操作符​

isn模块提供了标准的比较操作符,外加对所有这些数据类型的 B 树和哈希索引支持。此外还有一些特殊的函数,它们展示在表 D.12中。在这个表中,isn意味着该模块的数据类型中的任何一种。

表 D.12. isn 函数


函数/描述

isn_weak ( boolean ) → boolean 设置弱输入模式(返回新设置)

isn_weak () → boolean 返回弱模式的当前状态

make_valid ( isn ) → isn 验证一个非法号码(清除非法标志)

is_valid ( isn ) → boolean 检查非法标志的存在​

弱模式被用来允许插入非法数据到一个表中。非法意味着校验位错误,而不是有丢失号码。

为什么你会想要使用弱模式?你可能有一个巨大的 ISBN 号码集合并且出于某种奇怪的原因其中具有错误的校验位。不管怎样,重点是你可能希望清理这些混乱,但是你仍然想要能够把 这些号码放在你的数据库中并且可能会使用一个外部工具在数据库中定位非法号码,这样你能够更容易地验证信息。因此你可能会想要在表中选择所有非法的号码。

当你使用弱模式在一个表中插入非法号码时,被插入的号码将会被加上修正过的校验位,但是它的最后将会有一个感叹号(!),例如0-11-000322-5!。这种非法标志符可以用is_valid函数检查并且可以用make_valid函数清除。即使不在弱模式中,你也能通过在号码某位追加!字符来强制非法号码的插入。另一个特殊特性是在输入过程中,你可以写一个?代替校验位,然后正确的校验位将被自动插入。

例子​

--直接使用类型:
test=# SELECT isbn('978-0-393-04002-9');
isbn
---------------
0-393-04002-X
(1 row)
test=# SELECT isbn13('0901690546');
isbn13
-------------------
978-0-901690-54-8
(1 row)
test=# SELECT issn('1436-4522');
issn
-----------
1436-4522
(1 row)
--转换类型:
-- 注意只有在号码处于另一种类型的合法值之中时,才能从 EAN13 转换成另一种类型
-- 因此下面的用法将不会工作:
test=# select isbn(ean13('0220356483481'));
ERROR: cannot cast EAN13(UPC) to ISBN for number: "0220356483481"
-- 但是下面的可以:
test=# SELECT upc(ean13('0220356483481'));
upc
--------------
220356483481
(1 row)
test=# SELECT ean13(upc('220356483481'));
ean13
-----------------
022-035648348-1
(1 row)
--创建一个表,它有一个单一列来保存 ISBN 号码:
CREATE TABLE test (id isbn);
INSERT INTO test VALUES('9780393040029');
--自动计算校验位(观察 '?'):
INSERT INTO test VALUES('220500896?');
INSERT INTO test VALUES('978055215372?');
test=# SELECT issn('3251231?');
issn
-----------
3251-2317
(1 row)
test=# SELECT ismn('979047213542?');
ismn
---------------
M-47213-542-3
(1 row)
--使用弱模式:
SELECT isn_weak(true);
INSERT INTO test VALUES('978-0-11-000533-4');
INSERT INTO test VALUES('9780141219307');
INSERT INTO test VALUES('2-205-00876-X');
test=# SELECT isn_weak(false);
isn_weak
----------
f
(1 row)
test=# SELECT id FROM test WHERE NOT is_valid(id);
id
----------------
0-11-000533-3!
0-14-121930-0!
2-205-00876-5!
(3 rows)
test=# UPDATE test SET id = make_valid(id) WHERE id = '2-205-00876-X!';
UPDATE 1
test=# SELECT * FROM test;
id
----------------
0-393-04002-X
2-205-00896-X
0-552-15372-9
0-11-000533-3!
0-14-121930-0!
2-205-00876-5
(6 rows)
test=# SELECT isbn13(id) FROM test;
isbn13
--------------------
978-0-393-04002-9
978-2-205-00896-8
978-0-552-15372-0
978-0-11-000533-1!
978-0-14-121930-1!
978-2-205-00876-0
(6 rows)

lo​

lo模块提供管理大对象(也被称为 LO 或 BLOB)的支持。这包括一种数据类型lo以及一个触发器lo_manage。

这个模块被视为"trusted",也就是说,它能够由当前数据库中具有CREATE特权的非超级用户来安装。

原理​

JDBC 驱动的问题之一(并且这也影响 ODBC 驱动)是其说明书假定对 BLOB(二进制大对象)的引用被存储在一个表中,并且如果该项被改变相关的 BLOB 会被从数据库删除。

但对于Halo来说这并不会发生。大对象被当做自主的对象,一个表项可以通过 OID 引用一个大对象,也可以有多个表项引用同一个大对象 OID,因此系统不会因为你改变或者删除这种项而删除大对象。

现在这对Halo-相关的应用挺好的,但是使用 JDBC 或 ODBC 的标准代码不会删除那些对象,从而导致孤立对象 --- 不被任何东西引用的对象,而且会占据磁盘空间。

lo允许通过附加一个触发器到包含 LO 引用列的表来修复这种问题。该触发器本质上只是在你删除或修改一个引用大对象的值时做lo_unlink。当你使用这个触发器时,你必须假定在一个触发器控制的列中只有一个对任意大对象的数据库引用!

这个模块也提供了一种数据类型lo,它实际上只是oid类型的一个域。这有助于区分保存大对象引用的数据库列和保存其他东西 OID 的列。你并不是必须使用lo类型来使用该触发器,但是用它来追踪数据库中哪些列表示你要用触发器管理的大对象非常方便。也有传言说如果你不为 BLOB 列使用lo,ODBC 驱动会感到困惑。

如何使用它​

这里是一个简单的用法示例:

test=# CREATE TABLE image (title text, raster lo);
CREATE TABLE
test=# CREATE TRIGGER t_raster BEFORE UPDATE OR DELETE ON image
test-# FOR EACH ROW EXECUTE FUNCTION lo_manage(raster);
CREATE TRIGGER

对每一个将包含到大对象唯一引用的列,创建一个BEFORE UPDATE OR DELETE触发器,并且将该列名作为唯一的触发器参数。你也可以用BEFORE UPDATE OF column_name来限制该触发器只对该列上的更新事件执行。如果你需要在同一个表中有多个lo列,为每一个创建一个独立的触发器,记住为同一个表上的每个触发器指定一个不同的名称。

限制​

• 删除一个表仍将让它包含的任何对象变成孤立的,因为触发器在这种情况下不会被执行。你可以在DROP TABLE之前放上DELETE FROM table来避免这种问题。

TRUNCATE有同样的危害。

偶尔运行vacuumlo作为lo_manage触发器的后备是个好主意。

• 有些前端可能会创建它们自己的表,并且将不会创建相关的触发器。另外,用户可能不会记得(或知道)要创建触发器。

ltree​

这个模块实现了一种数据类型ltree用于表示存储在一个层次树状结构中的数据的标签。还提供了在标签树中搜索的扩展功能。

这个模块被视为"trusted",也就是说,它可以是由拥有CREATE特权的非超级用户安装在当前数据库上。

定义​

一个标签是一个字母数字字符和下划线的序列(例如,在 C 区域中允许字符A-Za-z0-9_)。

标签长度必须少于 256 字符。

例子:42, Personal_Services

一个标签路径是由点号分隔的零个或者更多个标签的序列,例如L1.L2.L3,它表示一个从层次树的根到一个特定节点的路径。一个标签路径的长度不能超过65535标签。

例子:Top.Countries.Europe.Russia

ltree模块提供多种数据类型:

• ltree存储一个标签路径。

• lquery表示一个用于匹配ltree值的类正则表达式的模式。一个简单词匹配一个路径中的那个标签。 一个星号(*)匹配零个或更多个标签。它们可以用点连接起来,以形成一个必须匹配整个标签路径的模式。例如:

foo 正好匹配标签路径foo

*.foo.* 匹配任何包含标签foo的标签路径

*.foo 匹配任何最后一个标签是foo的标签路径

星号和简单词都可以被用来限制它能匹配多少标签:

*{n} 匹配正好n个标签

*{n,} 匹配至少n个标签

*{n,m} 匹配至少n个但是最多m个标签

*{,m} 匹配最多m个标签 --- 与*{0,m}相同

foo{n,m} 匹配至少 n 但是不超过 m 并发的 foo

foo{,} 匹配任何数量的并发的 foo, 包括零

在缺乏任何显式量词的情况下,星号的默认值是匹配任意数量的标签(也就是{,}),而非星号项的默认值是只匹配一次(也就是{1})。

有几个修饰符可以放在一个非星号的lquery项的末尾,使它能匹配除了精确匹配之外更多的匹配:

@ 不区分大小写匹配,例如a@匹配A

* 匹配带此前缀的任何标签,例如foo*匹配foobar

% 匹配开头以下划线分隔的词

%的行为有点复杂。它尝试匹配词而不是整个标签。例如,foo_bar%匹配foo_bar_baz但是不匹配foo_barbaz。如果和*组合,前缀匹配可以单独应用于每一个词,例如foo_bar%*匹配foo1_bar2_baz但不匹配foo1_br2_baz。

此外,你可以写多个带有|(OR)的可能改过的非星号项目来匹配那些项目中的任何一个(或几个),并且你可以在非星号组最前面放上!(NOT)来匹配任何不匹配那些分支的标签。量词,若有的话,位于组的末尾;它意味着作为一个整体的组的一些匹配(也就是说,一些匹配或不匹配任何替代的标签)。

这里是一个lquery的例子:

Top.*{0,2}.sport*@.!football|tennis{1,}.Russ*|Spain
a. b. c. d. e.

这个查询将匹配任何这样的标签路径:

a. 开始于标签Top

b. 并且接着具有 0 到 2 个标签

c. 之后是一个开始于大小写无关的前缀sport的标签

d. 然后有一个或多个标签,没有匹配football和tennis。

e. 并且结尾是一个开始于Russ的标签,或者完全匹配Spain的标签。

• ltxtquery表示一种用于匹配ltree值的类全文搜索的模式。一个ltxtquery值包含词,也可能在末尾带有修饰符@、*、%,修饰符具有和lquery中相同的含义。词可以用&(AND)、|(OR)、!(NOT)以及圆括号组合。lquery和ltxtquery的关键区别是前者匹配词时不考虑它们在标签路径中的位置。

这是一个ltxtquery的例子:

Europe & Russia*@ & !Transportation

这将匹配包含标签Europe以及任何以Russia开始(大小写不敏感)的标签的路径,但是不匹配包含标签Transportation的路径。这些词在路径中的位置并不重要。还有,当使用%时,该次可以与一个标签中任何下划线分隔的词匹配,而不管它们的位置如何。

注意:ltxtquery允许符号之间的空白,但是ltree和lquery不允许。

操作符和函数​

类型ltree有普通比较操作符 =、<>、 <、>、<=、>=。 比较会按照树遍历的顺序排序,一个节点的子女按照标签文本排序。另外,还有表 D.13中显示的特殊操作符。

表 D.13. ltree 操作符

操作符/描述:

  • ltree @> ltree → boolean:左参数是右参数(或相等)的
  • ltree <@ ltree → boolean:左参数是右参数(或相等)的
  • ltree ~ lquery → boolean
  • lquery ~ ltree → boolean:ltree 匹配 lquery
  • ltree ? lquery[] → boolean
  • lquery[] ? ltree → boolean:ltree 在数组中匹配任何 lquery
  • ltree @ ltxtquery → boolean
  • ltxtquery @ ltree → boolean:ltree 匹配 ltxtquery ltree \ 连接 ltree 路径。 ltree \ text \ 把文本转换为 ltree 并连接。
  • ltree[] @> ltree → boolean
  • ltree <@ ltree[] → boolean:数组中包含一个 ltree的
  • ltree[] <@ ltree → boolean
  • ltree @> ltree[] → boolean:数组中包含一个 ltree的
  • ltree[] ~ lquery → boolean
  • lquery ~ ltree[] → boolean:数组中包含匹配 lquery的任何路径
  • ltree[] ? lquery[] → boolean
  • lquery[] ? ltree[] → boolean:Does ltree 数组中包含匹配任何lquery的任何路径
  • ltree[] @ ltxtquery → boolean
  • ltxtquery @ ltree[] → boolean:数组中包含匹配ltxtquery的任何路径
  • ltree[] ?@> ltree → ltree:返回作为ltree的第一个数组条目,如果没有则返回NULL。
  • ltree[] ?<@ ltree → ltree:返回作为ltree的第一个数组条目,如果没有则返回NULL。
  • ltree[] ?~ lquery → ltree:返回匹配lquery的第一个数组条目,如果没有,则返回NULL。
  • ltree[] ?@ ltxtquery → ltree:返回匹配ltxtquery的第一个数组条目,如果没有,则返回NULL。

操作符<@、@>、 @以及~有类似的、 ^<@、^@>、^@、 ^~,只是它们不适用索引。它们只对测试目的有用。

可用的函数在表 D.14中。

表 D.14. ltree 函数

函数/描述:/例子:

  • subltree ( ltree, start integer, end integer ) → ltree:返回从位置start到位置end-1的ltree的子路径(从0开始计数)。
  • subltree('Top.Child1.Child2', 1, 2) → Child1
  • subpath ( ltree, offset integer, len integer ) → ltree:返回从位置offset开始的ltree的子路径,长度为len。 如果offset为负,则子路径从距离路径终点的远端开始。如果len为负,将许多标签留在路径的末尾。
  • subpath('Top.Child1.Child2', 0, 2) → Top:.Child1
  • subpath ( ltree, offset integer ) → ltree:返回从位置offset开始的ltree的子路径,扩展到路径的结束。 如果offset为负,则子路径从距离路径终点的远端开始。
  • subpath('Top.Child1.Child2', 1) → Child1:.Child2
  • nlevel ( ltree ) → integer:返回路径中标签的数量。 nlevel('Top.Child1.Child2') → 3
  • index ( a ltree, b ltree ) → integer:返回b在a中第一次出现的位置,如果没有发现则返回-1。 index('0.1.2.3.5.4.5.6.8.5.6.8', '5.6') → 6
  • index ( a ltree, b ltree, offset integer ) → integer:返回b在a中第一次出现的位置,如果没有发现则返回-1。搜索从位置offset开始;负的offset是指从路径的末端开始的-offset标签。 index('0.1.2.3.5.4.5.6.8.5.6.8', '5.6', -4) → 9
  • text2ltree ( text ) → ltree:转换 text 到 ltree。
  • ltree2text ( ltree ) → text:转换 ltree 到 text。
  • lca ( ltree [, ltree [, ... ]] ) → ltree:计算路径的最长公共祖先(最多可支持8个参数)。 lca('1.2.3', '1.2.3.4.5.6') → 1.2
  • lca ( ltree[] ) → ltree:计算数组中的路径的最长公共祖先。 lca(array['1.2.3'::ltree,'1.2.3.4']) → 1.2

索引​

ltree支持一些能加速上述操作符的索引类型:

• ltree上的 B-树索引: <、<=、=、 >=、>

• ltree 之上的GiST索引(gist_ltree_ops opclass): <、<=、=、 >=、>、 @>、<@、@、~、?

gist_ltree_ops GiST opclass将一组路径标签近似计算为位图签名。它的可选整数参数siglen决定了签名的字节长度。默认签名长度为8字节。签名长度的有效值在1到2024字节之间。更长的签名将导致更精确的搜索(扫描更小的索引部分和更少的堆页),但代价是更大的索引。

创建默认签名长度为8字节的索引的例子:

CREATE INDEX path_gist_idx ON test USING GIST (path);

创建签名长度为100字节的索引的例子:

CREATE INDEX path_gist_idx ON test USING GIST (path
gist_ltree_ops(siglen=100));

• ltree[] 之上的GiST索引(gist__ltree_ops opclass): ltree[] <@ ltree、ltree @>ltree[]、 @、~、?

gist__ltree_ops GiST opclass 的工作类似于gist_ltree_ops 并且也使用签名长度作为参数。 gist__ltree_ops中的siglen的默认值为28字节。

创建这样一个默认签名长度为28字节的索引的例子:

CREATE INDEX path_gist_idx ON test USING GIST (array_path);

创建这样一个签名长度为100字节的索引的例子:

CREATE INDEX path_gist_idx ON test USING GIST (array_path
gist__ltree_ops(siglen=100));

注意:这种索引类型是有损的。

例子​

这个例子使用下列数据:

CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
INSERT INTO test VALUES ('Top.Hobbies');
INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
INSERT INTO test VALUES ('Top.Collections');
INSERT INTO test VALUES ('Top.Collections.Pictures');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
CREATE INDEX path_gist_idx ON test USING GIST (path);
CREATE INDEX path_idx ON test USING BTREE (path);

现在,我们有一个表test,它被填充了描述下列层次的数据:

Top

/ | \\
Science Hobbies Collections
/ | \\
Astronomy Amateurs_Astronomy Pictures
/ \\ |
Astrophysics Cosmology Astronomy
/ | \\
Galaxies Stars Astronauts

我们可以做继承:

test=# SELECT path FROM test WHERE path <@ 'Top.Science';
path
------------------------------------
Top.Science
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
(4 rows)

这里是一些路径匹配的例子:

test=# SELECT path FROM test WHERE path ~ '*.Astronomy.*';
path
-----------------------------------------------
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
Top.Collections.Pictures.Astronomy
Top.Collections.Pictures.Astronomy.Stars
Top.Collections.Pictures.Astronomy.Galaxies
Top.Collections.Pictures.Astronomy.Astronauts
(7 rows)
test=# SELECT path FROM test WHERE path @'Astro*&!pictures@ ';
path
------------------------------------
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
(3 rows)

这里是一些全文搜索的例子:

test=# SELECT path FROM test WHERE path @ 'Astro*% & !pictures@';
path
------------------------------------
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
Top.Hobbies.Amateurs_Astronomy
(4 rows)
test=# SELECT path FROM test WHERE path @ 'Astro* & !pictures@';
path
------------------------------------
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
(3 rows)

使用函数的路径构建:

test=# SELECT subpath(path,0,2)||'Space'||subpath(path,2) FROM test test-# WHERE
test-# path <@ 'Top.Science.Astronomy';
?column?
------------------------------------------
Top.Science.Space.Astronomy
Top.Science.Space.Astronomy.Astrophysics
Top.Science.Space.Astronomy.Cosmology
(3 rows)

我们可以通过常见一个在路径中指定位置插入标签的 SQL 函数来简化:

test=# CREATE FUNCTION ins_label(ltree, int, text) RETURNS ltree
test-# AS 'select subpath(\$1,0,\$2) || \$3 || subpath(\$1,\$2);'
test-# LANGUAGE SQL IMMUTABLE;
CREATE FUNCTION
test=# SELECT ins_label(path,2,'Space') FROM test WHERE path <@ 'Top.Science.Astronomy';
ins_label
------------------------------------------
Top.Science.Space.Astronomy
Top.Science.Space.Astronomy.Astrophysics
Top.Science.Space.Astronomy.Cosmology
(3 rows)

转换​

有一些额外的扩展为 PL/Python 实现了ltree类型的转换。

这些扩展是ltree_plpythonu、ltree_plpython2u 以及ltree_plpython3u。如果安装了这些转换并且在创建函数时指定了它们,ltree值会被映射为Python 列表(不过,当前并不支持逆向的转换)。

小心:强烈建议转换扩展安装在与ltree相同的模式中。否则,如果转换扩展的模式包含恶意用户定义的对象,就会存在安装时的安全隐患。

pageinspect​

pageinspect模块提供函数让你从低层次观察数据库页面的内容,这对于调试目的很有用。所有这些函数只能被超级用户使用。

通用函数​

get_raw_page(relname text, fork text, blkno int) 返回 bytea

get_raw_page读取提及的关系中的指定块并且以一个bytea值的形式返回一个拷贝。这允许得到该块的一个单一的时间一致的拷贝。对于主数据分叉,fork应该是'main',对于空闲空间映射应该是'fsm',对于可见性映射应该是'vm',对于初始化分叉应该是'init'。

get_raw_page(relname text, blkno int) 返回 bytea

一个简写版的get_raw_page,用于读取主分叉。等效于get_raw_page(relname, 'main',blkno)

page_header(page bytea) 返回 record

page_header显示所有Halo堆和索引页面的公共域。

用get_raw_page获得的一个页面映像应该作为参数传递。例如:

test=# \x on
扩展显示已打开
test=# SELECT * FROM page_header(get_raw_page('pg_class', 0));
-[ RECORD 1 ]---------
lsn | 0/2AC62DD0
checksum | 0
flags | 1
lower | 212
upper | 5808
special | 8192
pagesize | 8192
version | 4
prune_xid | 34449

返回的列对应于PageHeaderData结构中的域。

checksum域是存放在页面中的校验和,如果页面被损坏它可能是不正确的。如果对这个实例没有启用数据校验和,则存储的这个值没有意义。

page_checksum(page bytea, blkno int4) returns smallint

page_checksum为页面计算校验和,就像它被放置在给定块上一样。

应该将get_raw_page得到的页面映像作为参数传入。例如:

test=# SELECT page_checksum(get_raw_page('pg_class', 0), 0);
page_checksum
---------------
17728
(1 row)

注意:校验和取决于块号,因此应该将匹配的块号传入(除非在做调试)。

用这个函数计算的校验和可以拿来和函数page_header的结果域checksum进行比较。如果为这个实例启用了数据校验和,则两个值应该相等。

fsm_page_contents(page bytea) returns text

fsm_page_contents显示FSM页面的内部节点结构。例如:

=# SELECT fsm_page_contents(get_raw_page('pg_class', 'fsm', 0));

输出为多行字符串,页面中的二进制树中每个节点有一行。 仅有不为零的节点会被打印。 所谓的"next"指针,指向页面中下一个要返回的槽,也会被打印。

Heap Functions​

heap_page_items(page bytea) 返回 setof record

heap_page_items显示一个堆页面上所有的行指针。对那些使用中的行指针,元组头部和元组原始数据也会被显示。不管元组对于拷贝原始页面时的 MVCC 快照是否可见,它们都会被显示。

用get_raw_page获得的一个堆页面映像应该作为参数传递。例如:

test=# SELECT * FROM heap_page_items(get_raw_page('pg_class', 0));

heap_tuple_infomask_flags函数可用于为堆元组解包t_infomask 和t_infomask2的标志位。

tuple_data_split(rel_oid oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text [, do_detoast bool]) returns bytea[]

tuple_data_split以后端内部的相同方式将元组数据拆解成属性。

test=# SELECT tuple_data_split('pg_class'::regclass, t_data, t_infomask,
t_infomask2, t_bits) FROM heap_page_items(get_raw_page('pg_class', 0));

应该用与heap_page_items的返回属性相同的参数来调用这个函数。

如果do_detoast是true,则根据需要将把属性解除TOAST。默认值为false。

heap_page_item_attrs(page bytea, rel_oid regclass [, do_detoast bool]) returns
setof record

heap_page_item_attrs等效于 heap_page_items,不过它会把元组原始数据返回为属性的数组,如果do_detoast为真( 默认为false),这些属性会被反 TOAST。

应该把用get_raw_page得到的一个堆页面映像作为参数传入。例如:

test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0),
'pg_class'::regclass);
heap_tuple_infomask_flags(t_infomask integer, t_infomask2 integer) returns record

heap_tuple_infomask_flags将heap_page_items返回的 t_infomask和t_infomask2解码为一组由标志名称组成的可读数组,一列用于所有标志,一列用于组合标志。 例如:

test=# SELECT t_ctid, raw_flags, combined_flags
FROM heap_page_items(get_raw_page('pg_class', 0)),
LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2)
WHERE t_infomask IS NOT NULL OR t_infomask2 IS NOT NULL;

应该使用与heap_page_items的返回属性相同的参数调用此函数。

为考虑多个原始位值的源级宏显示组合标志,例如HEAP_XMIN_FROZEN。

B树函数​

bt_metap(relname text) 返回 record

bt_metap返回关于一个B树索引元页的信息。例如:

test=# \x on
扩展显示已打开
test=# SELECT * FROM bt_metap('pg_cast_oid_index');
-[ RECORD 1 ]-------------+-------
magic | 340322
version | 4
root | 1
level | 0
fastroot | 1
fastlevel | 0
last_cleanup_num_delpages | 0
last_cleanup_num_tuples | -1
allequalimage | t

bt_page_stats(relname text, blkno int) 返回 record

bt_page_stats返回有关 B-树索引单一页面的总计信息。例如:

test=# \x on
扩展显示已打开
test=# SELECT * FROM bt_page_stats('pg_cast_oid_index', 1);
-[ RECORD 1 ]-+-----
blkno | 1
type | l
live_items | 254
dead_items | 0
avg_item_size | 16
page_size | 8192
free_size | 3068
btpo_prev | 0
btpo_next | 0
btpo_level | 0
btpo_flags | 3

bt_page_items(relname text, blkno int) 返回 setof record

bt_page_items返回一个 B-树索引页面上项的所有细节信息。例如:

test=# \x on
扩展显示已打开
test=# SELECT itemoffset, ctid, itemlen, nulls, vars, data, dead, htid,
tids[0:2] AS some_tids
FROM bt_page_items('pg_cast_oid_index', 1) LIMIT 3;
-[ RECORD 1 ]-----------------------
itemoffset | 1
ctid | (0,1)
itemlen | 16
nulls | f
vars | f
data | 10 27 00 00 00 00 00 00
dead | f
htid | (0,1)
some_tids |
-[ RECORD 2 ]-----------------------
itemoffset | 2
ctid | (0,2)
itemlen | 16
nulls | f
vars | f
data | 11 27 00 00 00 00 00 00
dead | f
htid | (0,2)
some_tids |
-[ RECORD 3 ]-----------------------
itemoffset | 3
ctid | (0,3)
itemlen | 16
nulls | f
vars | f
data | 12 27 00 00 00 00 00 00
dead | f
htid | (0,3)
some_tids |
(显示前 3 条记录)```

这是一个 B 树的叶子页面。指向该表的所有元组是发布列表元组(所有这些元组总共存储 100 个 6 字节 TID)。在itemoffset编号 1 处还有一个"high key"元组。ctid用于存储本示例中每个元组的编码信息,尽管叶页元组通常将堆 TID 直接存储在ctid字段中,而不是tids是存储为发布列表的 TID 列表。

在内部页面(未显示)中,ctid的块号部分是一个"downlink", 它是索引本身中另一个页面的块号。ctid的偏移部分(第二个数字)存储有关元组的编码信息,例如:存在的列数(后缀截断可能已删除不需要的后缀列)。截断的列被视为具有值"minus infinity"。

htid显示元组的堆 TID,无论底层元组表示如何。该值可能与ctid匹配,或者可以从发布列表元组和来自内部页面的元组所使用的替代表示中解码。内部页面中的元组通常会截断实现级别的堆 TID 列,这表示为 NULLhtid值。

注意:在任何非最右页面(页面的btpo_next域中有非零值)上的第一个项是该页的"high key",表示它的 data是作为该页面上所有项的一个上界存在,而它的 ctid字段不指向另一个块。此外,在内部页面上,第一个真正的数据项(第一个不是高键的项)可靠地将每一列都截断了,在其data字段中没有留下任何实际值。不过,这样一个项确实 在其有ctid域中有向下的链接。

有关 B 树索引结构的更多详细信息。

bt_page_items(page bytea) returns setof record

还可以把一个页面以bytea值的形式传递给bt_page_items。应该将get_raw_page得到的页面映像作为参数传入。因此,上一个例子可以被重写成这样:

```shell-session
test=# \x on
扩展显示已打开
test=# SELECT itemoffset, ctid, itemlen, nulls, vars, data, dead, htid,
tids[0:2] AS some_tids
FROM bt_page_items(get_raw_page('pg_cast_oid_index', 1)) LIMIT 3;
-[ RECORD 1 ]-----------------------
itemoffset | 1
ctid | (0,1)
itemlen | 16
nulls | f
vars | f
data | 10 27 00 00 00 00 00 00
dead | f
htid | (0,1)
some_tids |
-[ RECORD 2 ]-----------------------
itemoffset | 2
ctid | (0,2)
itemlen | 16
nulls | f
vars | f
data | 11 27 00 00 00 00 00 00
dead | f
htid | (0,2)
some_tids |
-[ RECORD 3 ]-----------------------
itemoffset | 3
ctid | (0,3)
itemlen | 16
nulls | f
vars | f
data | 12 27 00 00 00 00 00 00
dead | f
htid | (0,3)
some_tids |
(显示前 3 条记录)```

所有其他细节和前一项中的解释相同。

#### BRIN函数

brin_page_type(page bytea) returns text

brin_page_type返回一个给定的 BRIN索引页面的页面类型,如果该页面不是一个合法的BRIN页面则抛出错误。例如:

```shell-session
test=# SELECT brin_page_type(get_raw_page('brinidx', 0));
brin_page_type
----------------
meta
brin_metapage_info(page bytea) returns record

brin_metapage_info返回有关一个 BRIN索引元页的各类信息。例如:

test=# SELECT * FROM brin_metapage_info(get_raw_page('brinidx', 0));
magic | version | pagesperrange | lastrevmappage
------------+---------+---------------+----------------
0xA8109CFA | 1 | 4 | 2
brin_revmap_data(page bytea) returns setof tid

brin_revmap_data返回一个 BRIN索引范围映射页面中元组标识符的列表。 例如:

test=# SELECT * FROM brin_revmap_data(get_raw_page('brinidx', 2)) LIMIT 5;
pages
---------
(6,137)
(6,138)
(6,139)
(6,140)
(6,141)
brin_page_items(page bytea, index oid) returns setof record

brin_page_items返回存储在 BRIN数据页面中存储的数据。例如:

test=# SELECT * FROM brin_page_items(get_raw_page('brinidx', 5),
'brinidx')
ORDER BY blknum, attnum LIMIT 6;
itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | value
------------+--------+--------+----------+----------+-------------+----
137 | 0 | 1 | t | f | f |
137 | 0 | 2 | f | f | f | {1 .. 88}
138 | 4 | 1 | t | f | f |
138 | 4 | 2 | f | f | f | {89 ..176}
139 | 8 | 1 | t | f | f |
139 | 8 | 2 | f | f | f | {177 ..264}

返回的列对应于BrinMemTuple和 BrinValues结构中的域。

GIN函数​

gin_metapage_info(page bytea) returns record

gin_metapage_info返回有关一个 GIN索引元页的信息。例如:

test=# SELECT * FROM gin_metapage_info(get_raw_page('gin_index', 0));
-[ RECORD 1 ]----+-----------
pending_head | 4294967295
pending_tail | 4294967295
tail_free_size | 0
n_pending_pages | 0
n_pending_tuples | 0
n_total_pages | 7
n_entry_pages | 6
n_data_pages | 0
n_entries | 693
version | 2
gin_page_opaque_info(page bytea) returns record

gin_page_opaque_info返回有关一个 GIN索引不透明区域的信息,如页面类型等。例如:

test=# SELECT * FROM gin_page_opaque_info(get_raw_page('gin_index', 2));
rightlink | maxoff | flags
-----------+--------+------------------------
5 | 0 | {data,leaf,compressed}
(1 row)
gin_leafpage_items(page bytea) returns setof record

gin_leafpage_items返回有关存储在一个 GIN叶子页面中的数据的信息。例如:

test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
FROM gin_leafpage_items(get_raw_page('gin_test_idx', 2));
first_tid | nbytes | some_tids
-----------+--------+-----------------------------------------------------
(8,41) | 244 | {"(8,41)","(8,43)","(8,44)","(8,45)","(8,46)"}
(10,45) | 248 | {"(10,45)","(10,46)","(10,47)","(10,48)","(10,49)"}
(12,52) | 248 | {"(12,52)","(12,53)","(12,54)","(12,55)","(12,56)"}
(14,59) | 320 | {"(14,59)","(14,60)","(14,61)","(14,62)","(14,63)"}
(167,16) | 376 |
{"(167,16)","(167,17)","(167,18)","(167,19)","(167,20)"}
(170,30) | 376 |
{"(170,30)","(170,31)","(170,32)","(170,33)","(170,34)"}
(173,44) | 197 |
{"(173,44)","(173,45)","(173,46)","(173,47)","(173,48)"}
(7 rows)

Hash函数​

hash_page_type(page bytea) returns text

hash_page_type返回给定的HASH索引页面的页面类型。例如:

test=# SELECT hash_page_type(get_raw_page('con_hash_index', 0));
hash_page_type
----------------
metapage
hash_page_stats(page bytea) returns setof record

hash_page_stats返回有关一个HASH索引的信息。例如:

test=# SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1));
-[ RECORD 1 ]---+-----------
live_items | 407
dead_items | 0
page_size | 8192
free_size | 8
hasho_prevblkno | 4096
hasho_nextblkno | 8474
hasho_bucket | 0
hasho_flag | 66
hasho_page_id | 65408
hash_page_items(page bytea) returns setof record

hash_page_items返回有关一个HASH索引的桶页或者溢出页中存储的数据的信息。例如:

test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)) LIMIT
5;
itemoffset | ctid | data
------------+-----------+------------
1 | (899,77) | 1053474816
2 | (897,29) | 1053474816
3 | (894,207) | 1053474816
4 | (892,159) | 1053474816
5 | (890,111) | 1053474816
hash_bitmap_info(index oid, blkno int) returns record

hash_bitmap_info返回HASH索引一个特定在页中的状态。例如:

=# SELECT * FROM hash_bitmap_info('con_hash_index', 2052);
bitmapblkno | bitmapbit | bitstatus
-------------+-----------+-----------
65 | 3 | thash_metapage_info(page bytea) returns record

hash_metapage_info返回一个HASH索引的元页中存放的信息。例如:

=# SELECT magic, version, ntuples, ffactor, bsize, bmsize, bmshift,

-# maxbucket, highmask, lowmask, ovflpoint, firstfree, nmaps, procid,

-# regexp_replace(spares::text, '(,0)*}', '}') as spares,

-# regexp_replace(mapp::text, '(,0)*}', '}') as mapp

-# FROM hash_metapage_info(get_raw_page('con_hash_index', 0));
-[ RECORD 1 ]-------------------------------------------------------------------------------
magic | 105121344
version | 4
ntuples | 500500
ffactor | 40
bsize | 8152
bmsize | 4096
bmshift | 15
maxbucket | 12512
highmask | 16383
lowmask | 8191
ovflpoint | 28
firstfree | 1204
nmaps | 1
procid | 450
spares | {0,0,0,0,0,0,1,1,1,1,1,1,1,1,3,4,4,4,45,55,58,59,
508,567,628,704,1193,1202,1204}
mapp | {65}