38.16. 将相关对象打包到扩展中

PostgreSQL 的有用扩展通常包括多个 SQL 对象。例如,新的数据类型将需要新的功能,新的运算符,并可能需要新的索引运算符类。将所有这些对象收集到一个包中以简化数据库 Management 是很有帮助的。 PostgreSQL 将这样的包称为* extension 。要定义扩展,您至少需要一个 script 文件*,其中包含创建扩展对象的 SQL 命令;以及一个* control file *,用于指定扩展本身的一些基本属性。如果 extensions 包含 C 代码,则通常还会有一个共享库文件,其中已包含 C 代码。拥有这些文件后,只需执行简单的CREATE EXTENSION命令即可将对象加载到数据库中。

使用扩展而不是仅运行 SQL 脚本将一堆“松散”对象加载到数据库中的主要优点是,PostgreSQL 将随后了解扩展的对象在一起。您可以使用单个DROP EXTENSION命令删除所有对象(无需维护单独的“卸载”脚本)。更加有用的是,pg_dump 知道它不应该转储扩展的单个成员对象-它只会在转储中包含CREATE EXTENSION命令。这极大地简化了向新版本扩展的迁移,该版本可能包含比旧版本更多或不同的对象。但是请注意,将此类转储加载到新数据库时,必须具有扩展程序的控件,脚本和其他文件。

PostgreSQL 不允许您删除扩展中包含的单个对象,除非删除整个扩展。另外,尽管可以更改扩展成员对象的定义(例如,通过CREATE OR REPLACE FUNCTION表示功能),但请记住,pg_dump 不会转储修改后的定义。仅当您同时在扩展程序的脚本文件中进行相同的更改时,这种更改通常才有意义。 (但是对于包含配置数据的表有特殊规定;请参阅Section 38.16.4。)在生产情况下,通常最好创建扩展更新脚本以对扩展成员对象进行更改。

扩展脚本可以通过GRANTREVOKE语句对作为扩展一部分的对象设置特权。每个对象(如果已设置)的最终特权集将存储在pg_init_privs系统目录中。使用 pg_dump 时,转储中将包含CREATE EXTENSION命令,然后是将对象的特权设置为采用转储时所必需的GRANTREVOKE语句集。

PostgreSQL 当前不支持发布CREATE POLICYSECURITY LABEL语句的扩展脚本。这些扩展应该在创建扩展后设置。扩展对象上的所有 RLS 策略和安全标签将包含在 pg_dump 创建的转储中。

扩展机制还提供了用于打包修改脚本的规定,这些脚本可调整扩展中包含的 SQL 对象的定义。例如,如果扩展的 1.1 版添加了一个功能,而与 1.0 相比则更改了另一个功能的主体,则扩展作者可以提供* update 脚本*,该脚本仅进行这两项更改。然后,可以使用ALTER EXTENSION UPDATE命令应用这些更改并跟踪给定数据库中实际安装了该扩展程序的哪个版本。

ALTER EXTENSION的描述中显示了可以作为扩展成员的 SQL 对象的类型。值得注意的是,数据库扩展范围内的对象(例如数据库,角色和表空间)不能成为扩展成员,因为扩展仅在一个数据库中是已知的。 (尽管不禁止扩展脚本创建此类对象,但如果这样做,则不会将其作为扩展的一部分进行跟踪.)还请注意,尽管表可以是扩展的成员,但其附属对象(例如索引)却是不直接视为扩展的成员。另一个重要的一点是,模式可以属于扩展,但不能反过来:扩展本身具有不合格的名称,并且不存在于任何模式内。但是,扩展的成员对象只要适合于其对象类型,就属于模式。扩展拥有其成员对象所在的架构可能合适也可能不合适。

如果扩展的脚本创建了任何临时对象(例如临时表),则这些对象将被视为当前会话剩余时间的扩展成员,但会像任何临时对象一样在会话结束时被自动删除。这是一个 exception,即不能删除扩展成员而不删除整个扩展的规则。

38 .16.1. 定义扩展对象

分布广泛的扩展应该很少假设它们所占用的数据库。特别是,除非您发出SET search_path = pg_temp,否则假设每个不合格的名称都可以解析为恶意用户定义的对象。当心那些隐含地依赖search_path的构造:INCASE expression WHEN总是使用搜索路径选择一个运算符。使用OPERATOR(schema.=) ANYCASE WHEN expression代替它们。

38 .16.2. 扩展文件

CREATE EXTENSION命令依赖于每个 extensions 的控制文件,该文件的名称必须与带有_后缀的 extensions 相同,并且必须放置在安装的SHAREDIR/extension目录中。还必须至少有一个 SQL 脚本文件,该文件遵循命名模式extension--version.sql(例如对于 extensionsfoo的版本1.0foo--1.0.sql)。默认情况下,脚本文件也放置在SHAREDIR/extension目录中。但是控制文件可以为脚本文件指定其他目录。

扩展控制文件的文件格式与postgresql.conf文件的文件格式相同,即* parameter_name * = * value *分配的列表,每行一个。允许使用#引起的空白行和 Comments。确保引用的值不是单个单词或数字。

控制文件可以设置以下参数:

  • directory ( string )

    • 包含扩展的 SQL 脚本文件的目录。除非给出绝对路径,否则名称是相对于安装的SHAREDIR目录的。默认行为等同于指定directory = 'extension'
  • default_version ( string )

    • 扩展程序的默认版本(如果CREATE EXTENSION中未指定任何版本,则将安装的扩展程序)。尽管可以省略,但是如果没有出现VERSION选项,则将导致CREATE EXTENSION失败,因此您通常不希望这样做。
  • comment ( string )

    • 有关扩展的 Comments(任何字符串)。最初创建扩展时会应用 Comments,但扩展更新期间不会应用 Comments(因为这可能会覆盖用户添加的 Comments)。或者,可以通过在脚本文件中写入COMMENT命令来设置扩展的 Comments。
  • encoding ( string )

    • 脚本文件使用的字符集编码。如果脚本文件包含任何非 ASCII 字符,则应指定此项。否则,将假定文件采用数据库编码。
  • module_pathname ( string )

    • 此参数的值将替换脚本文件中每次出现的MODULE_PATHNAME。如果未设置,则不进行替换。通常,将其设置为$libdir/shared_library_name,然后在CREATE FUNCTION命令中将MODULE_PATHNAME用于 C 语言功能,因此脚本文件不需要硬连接共享库的名称。
  • requires ( string )

    • 此 extensions 所依赖的 extensions 的列表,例如requires = 'foo, bar'。必须先安装这些扩展,然后才能安装此扩展。
  • superuser ( boolean )

    • 如果此参数是true(默认设置),则只有超级用户才能创建 extensions 或将其更新为新版本。如果将其设置为false,则仅需要执行安装或更新脚本中的命令所需的特权。
  • relocatable ( boolean )

    • 如果可以在初始创建扩展后将其包含的对象移动到其他架构中,则该扩展是“可重定位的”。默认值为false,即 extensions 不可重定位。有关更多信息,请参见Section 38.16.3
  • schema ( string )

    • 只能为不可重定位的 extensions 设置此参数。它强制将 extensions 完全加载到命名模式中,而不加载到其他任何模式中。 schema参数仅在最初创建扩展时使用,而在扩展更新期间不使用。有关更多信息,请参见Section 38.16.3

除了主要控制文件extension.control之外,extensions 还可以具有以样式extension--version.control命名的辅助控制文件。如果提供,则这些文件必须位于脚本文件目录中。辅助控制文件遵循与主控制文件相同的格式。在安装或更新到该扩展版本时,在辅助控制文件中设置的任何参数都将覆盖主控制文件。但是,不能在辅助控制文件中设置参数directorydefault_version

扩展程序的 SQL 脚本文件可以包含任何 SQL 命令,但事务控制命令(BEGINCOMMIT等)和不能在事务块内部执行的命令(例如VACUUM)除外。这是因为脚本文件是在事务块内隐式执行的。

扩展程序的 SQL 脚本文件也可以包含以\echo开头的行,扩展机制将忽略它们(将其视为 Comments)。如果脚本文件被馈送到 psql 而不是通过CREATE EXTENSION加载(请参见Section 38.16.7中的示例脚本),则通常将此规定引发错误。否则,用户可能会意外地将扩展程序的内容作为“松散”对象而不是作为扩展程序加载,这种状态很难恢复。

尽管脚本文件可以包含指定编码所允许的任何字符,但是控制文件应仅包含纯 ASCII,因为 PostgreSQL 无法知道控制文件的 encodings。实际上,这仅是一个问题。在扩展程序的 Comments 中使用非 ASCII 字符。在这种情况下,建议的做法是不使用控制文件comment参数,而是在脚本文件中使用COMMENT ON EXTENSION设置 Comments。

38 .16.3. 扩展位置

用户通常希望将扩展中包含的对象加载到与扩展作者不同的架构中。支持三种可重定位级别:

  • 完全可重定位的扩展可以随时移动到另一个模式,即使已将其加载到数据库中也是如此。这是通过ALTER EXTENSION SET SCHEMA命令完成的,该命令会自动将所有成员对象重命名为新架构。通常,只有在扩展不包含有关其任何对象所处模式的内部假设时,才有可能。而且,扩展的对象必须全部以一个模式开头(忽略不属于任何模式的对象,例如程序语言)。通过在控制文件中设置relocatable = true来标记可完全重定位的 extensions。

  • extensions 可能在安装过程中可重定位,但在安装后无法重定位。如果扩展的脚本文件需要显式引用目标架构,例如在设置 SQL 函数的search_path属性时,通常就是这种情况。对于此类扩展,请在其控制文件中设置relocatable = false,然后使用@extschema@引用脚本文件中的目标架构。在执行脚本之前,该字符串的所有出现都将被实际目标架构的名称替换。用户可以使用CREATE EXTENSIONSCHEMA选项设置目标架构。

  • 如果扩展完全不支持重定位,请在其控制文件中设置relocatable = false,并将schema设置为预期目标模式的名称。这将防止使用CREATE EXTENSIONSCHEMA选项,除非它在控制文件中指定了相同的模式。如果扩展包含关于架构名称的内部假设,而这些假设不能由@extschema@的使用替代,则通常需要进行此选择。 @extschema@替换机制在这种情况下也可用,尽管由于模式名由控制文件确定,所以使用范围有限。

在所有情况下,脚本文件都将以初始设置为指向目标架构的search_path执行;也就是说,CREATE EXTENSION等效于:

SET LOCAL search_path TO @extschema@;

这允许脚本文件创建的对象进入目标架构。如果愿意,脚本文件可以更改search_path,但这通常是不希望的。 CREATE EXTENSION完成后,search_path将恢复为其先前的设置。

目标模式由控制文件中的schema参数(如果已给定)确定,否则由CREATE EXTENSIONSCHEMA选项(如果已给定)确定,否则由当前的默认对象创建模式(调用方search_path中的第一个)确定。当使用控制文件schema参数时,将创建目标架构(如果尚不存在),但在其他两种情况下,它必须已经存在。

如果控制文件的requires中列出了任何必备扩展,则它们的目标架构将附加到初始设置search_path。这使它们的对象对新扩展的脚本文件可见。

尽管不可重定位扩展可以包含分布在多个架构中的对象,但是通常希望将所有供外部使用的对象放入单个架构中,这被视为扩展的目标架构。在从属 extensions 的创建过程中,这种安排可以方便地与默认设置search_path一起使用。

38 .16.4. 扩展配置表

某些扩展包括配置表,其中包含可能在安装扩展后由用户添加或更改的数据。通常,如果表是扩展的一部分,则 pg_dump 都不会转储该表的定义或其内容。但是这种行为对于配置表来说是不可取的。用户所做的任何数据更改都需要包含在转储中,否则 extensions 在转储并重新加载后的行为会有所不同。

为了解决这个问题,扩展的脚本文件可以将其创建的表或序列标记为配置关系,这将导致 pg_dump 在转储中包含表或序列的内容(而不是其定义)。为此,例如,在创建表或序列后调用函数pg_extension_config_dump(regclass, text)

CREATE TABLE my_config (key text, value text);
CREATE SEQUENCE my_config_seq;

SELECT pg_catalog.pg_extension_config_dump('my_config', '');
SELECT pg_catalog.pg_extension_config_dump('my_config_seq', '');

可以用这种方式标记任意数量的表或序列。也可以标记与serialbigserial列关联的序列。

pg_extension_config_dump的第二个参数为空字符串时,表的全部内容将由 pg_dump 转储。通常只有在扩展脚本创建的表最初为空时,这才是正确的。如果表中混合了初始数据和用户提供的数据,则pg_extension_config_dump的第二个参数提供WHERE条件,该条件选择要转储的数据。例如,您可能会

CREATE TABLE my_config (key text, value text, standard_entry boolean);

SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entry');

然后确保standard_entry仅在扩展程序脚本创建的行中为 true。

对于序列,pg_extension_config_dump的第二个参数无效。

通过在配置表上创建触发器以确保正确标记已修改的行,可以处理更复杂的情况,例如用户可能会修改的最初提供的行。

您可以通过再次调用pg_extension_config_dump来更改与配置表关联的过滤条件。 (这通常在扩展更新脚本中很有用.)将表标记为不再是配置表的唯一方法是将其与 extensionsALTER EXTENSION ... DROP TABLE解除关联。

注意,这些表之间的外键关系将决定 pg_dump 转储表的 Sequences。具体来说,pg_dump 将尝试在引用表之前转储被引用的表。由于外键关系是在 CREATE EXTENSION 时(在将数据加载到表中之前)设置的,因此不支持循环依赖关系。当存在循环依赖性时,数据仍将转储出去,但转储将无法直接还原,并且需要用户干预。

serialbigserial列关联的序列需要直接标记为转储其状态。为此,仅仅标记他们的 parent 关系是不够的。

38 .16.5. 扩展更新

扩展机制的一个优点是,它提供了方便的方法来 Management 对定义扩展对象的 SQL 命令的更新。这是通过将版本名称或编号与扩展的安装脚本的每个发行版本相关联来完成的。此外,如果希望用户能够从一个版本动态更新数据库,则应提供* update 脚本*,该脚本会进行必要的更改以从一个版本更新到另一个版本。更新脚本的名称遵循模式extension--old_version--target_version.sql(例如foo--1.0--1.1.sql包含将 extensionsfoo的版本1.0修改为版本1.1的命令)。

如果有合适的更新脚本可用,则命令ALTER EXTENSION UPDATE会将安装的 extensions 更新为指定的新版本。更新脚本在CREATE EXTENSION为安装脚本提供的相同环境中运行:尤其是search_path以相同的方式设置,并且脚本创建的任何新对象都会自动添加到扩展中。同样,如果脚本选择删除扩展成员对象,则它们将自动与扩展分离。

如果扩展具有辅助控制文件,则用于更新脚本的控制参数是与脚本的目标(新)版本关联的参数。

更新机制可用于解决重要的特殊情况:将对象的“松散”集合转换为扩展。在将扩展机制添加到 PostgreSQL(在 9.1 中)之前,许多人编写了扩展模块,它们只是创建了各种未打包的对象。给定一个现有的包含此类对象的数据库,我们如何将这些对象转换为正确打包的扩展?删除它们然后执行简单的CREATE EXTENSION是一种方法,但是如果对象具有依赖项(例如,如果存在由 extensions 创建的数据类型的表列),则并不理想。解决此问题的方法是创建一个空扩展,然后使用ALTER EXTENSION ADD将每个先前存在的对象附加到该扩展,然后最终创建当前扩展版本中但未打包的发行版中没有的任何新对象。 CREATE EXTENSION通过其FROM * old_version 选项支持这种情况,这导致它不运行目标版本的常规安装脚本,而是运行名为extension--old_version--target_version.sql的更新脚本。尽管unpackaged是常见的约定,但由虚拟作者版本名称选择用作 old_version *取决于扩展作者。如果您有多个先前版本,则需要能够更新为扩展样式,请使用多个虚拟版本名称进行标识。

ALTER EXTENSION能够执行更新脚本文件序列以实现请求的更新。例如,如果仅foo--1.0--1.1.sqlfoo--1.1--2.0.sql可用,则当当前安装1.0时请求更新2.0版本时,ALTER EXTENSION将按 Sequences 应用它们。

PostgreSQL 对版本名称的属性不做任何假设:例如,它不知道1.1是否跟随1.0。它只是匹配可用的版本名称,并遵循需要应用最少更新脚本的路径。 (版本名称实际上可以是不包含--或前导或尾随-的任何字符串.)

有时提供“降级”脚本(例如foo--1.1--1.0.sql)以允许还原与版本1.1相关联的更改很有用。如果这样做,请注意降级脚本可能会意外地应用,因为它产生的路径较短。冒险的情况是,存在一个“快速路径”更新脚本,该脚本会跳过多个版本,而降级脚本会跳到快速路径的起点。与一次升级一个版本相比,应用降级然后是快速路径的步骤可能更少。如果降级脚本删除任何不可替换的对象,则将产生不良结果。

要检查意外的更新路径,请使用以下命令:

SELECT * FROM pg_extension_update_paths('extension_name');

这将显示指定 extensions 的每对不同的已知版本名称,以及从源版本到目标版本所采用的更新路径序列,如果没有可用的更新路径,则显示NULL。该路径以带有--分隔符的文本形式显示。如果您喜欢数组格式,则可以使用regexp_split_to_array(path,'--')

38 .16.6. 使用更新脚本安装扩展

已经存在了一段时间的扩展可能存在多个版本,为此作者需要编写更新脚本。例如,如果您在版本1.01.11.2中发布了fooextensions,则应该有更新脚本foo--1.0--1.1.sqlfoo--1.1--1.2.sql。在 PostgreSQL 10 之前,还必须创建直接构建较新扩展版本的新脚本文件foo--1.1.sqlfoo--1.2.sql,否则仅安装1.0然后更新就无法直接安装较新版本。这很繁琐且重复,但是现在没有必要了,因为CREATE EXTENSION可以自动跟踪更新链。例如,如果只有脚本文件foo--1.0.sqlfoo--1.0--1.1.sqlfoo--1.1--1.2.sql可用,则通过依次运行这三个脚本来满足安装版本1.2的请求。处理过程与首先安装1.0然后更新为1.2的过程相同。 (与ALTER EXTENSION UPDATE一样,如果有多个路径可用,则以最短路径为佳.)以这种方式安排扩展的脚本文件可以减少产生小更新所需的维护工作量。

如果您使用辅助(特定于版本)的控制文件,并且 extensions 保持这种样式,则请记住,即使每个版本都没有独立的安装脚本,每个版本都需要一个控制文件,因为该控制文件将确定隐式更新的方式执行该版本。例如,如果foo--1.0.control指定requires = 'bar'但未指定foo的其他控制文件,则从1.0更新到另一个版本时,extensions 对bar的依赖关系将被删除。

38 .16.7. 扩展示例

这是一个纯 SQL 扩展的完整示例,该扩展是一种由两个元素组成的复合类型,可以在其插槽中存储任何类型的值,这些值分别为“ k”和“ v”。非文本值会自动强制转换为文本进行存储。

脚本文件pair--1.0.sql如下所示:

-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION pair" to load this file. \quit

CREATE TYPE pair AS ( k text, v text );

CREATE OR REPLACE FUNCTION pair(text, text)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::@[email protected];';

CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, FUNCTION = pair);

-- "SET search_path" is easy to get right, but qualified names perform better.
CREATE OR REPLACE FUNCTION lower(pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW(lower($1.k), lower($1.v))::@[email protected];'
SET search_path = pg_temp;

CREATE OR REPLACE FUNCTION pair_concat(pair, pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k,
               $1.v OPERATOR(pg_catalog.||) $2.v)::@[email protected];';

控制文件pair.control如下所示:

# pair extension
comment = 'A key/value pair data type'
default_version = '1.0'
relocatable = false

尽管您几乎不需要 makefile 将这两个文件安装到正确的目录中,但是可以使用包含以下内容的Makefile

EXTENSION = pair
DATA = pair--1.0.sql

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

此 makefile 依赖于 PGXS,在Section 38.17中进行了描述。命令make install将控制和脚本文件安装到 pg_config 报告的正确目录中。

安装文件后,使用CREATE EXTENSION命令将对象加载到任何特定的数据库中。