37.17. 将相关对象打包成扩展 #
一个有用的 PostgreSQL 扩展通常包含多个 SQL 对象;例如,一种新的数据类型将需要新的函数、新的操作符,并且很可能还需要新的索引操作符类。把所有这些对象收集到一个单独的包中,有助于简化数据库管理。PostgreSQL 将这样的包称为扩展。要定义一个扩展,至少需要一个脚本文件,其中包含用于创建扩展对象的 SQL 命令,以及一个控制文件,用来指定扩展本身的一些基本属性。如果扩展包含 C 代码,通常还会有一个共享库文件,C 代码已被构建到其中。一旦准备好这些文件,只需执行一个简单的 CREATE EXTENSION 命令,就能把这些对象装入数据库。
使用扩展而不是仅仅运行 SQL 脚本把一堆“松散”对象装入数据库,最主要的优点在于 PostgreSQL
能够理解这些对象是属于同一个扩展的。你可以用一条 DROP EXTENSION 命令删除全部对象(无需维护单独的“卸载”脚本)。更有用的是,pg_dump 知道自己不应转储扩展的各个成员对象
— 它只会在转储中包含一条 CREATE EXTENSION
命令。这极大地简化了迁移到扩展新版本的过程,即使新版本包含比旧版本更多或不同的对象也是如此。不过要注意,在把这样的转储装载到新数据库时,必须能够访问该扩展的控制文件、脚本文件以及其他相关文件。
PostgreSQL 不允许你删除扩展中包含的单个对象,除非删除整个扩展。另外,虽然你可以修改扩展成员对象的定义(例如对函数使用 CREATE OR REPLACE FUNCTION),但要记住,被修改后的定义不会被 pg_dump 转储。这样的修改通常只有在你同时在扩展脚本文件中做出同样修改时才有意义。(不过,对于包含配置数据的表有特殊规定;见第 37.17.3 节。)在生产环境中,通常更好的做法是创建一个扩展更新脚本,用它来完成对扩展成员对象的修改。
扩展脚本可以使用 GRANT 和 REVOKE
语句,为属于扩展的对象设置权限。每个对象的最终权限集合(如果设置了权限)会存储在
pg_init_privs
系统目录中。使用 pg_dump 时,转储中会包含
CREATE EXTENSION 命令,后面再跟上一组必要的
GRANT 和 REVOKE 语句,以把对象权限恢复到生成该转储时的状态。
PostgreSQL 目前不支持扩展脚本发出
CREATE POLICY 或 SECURITY LABEL
语句。它们应当在扩展创建完成之后再设置。扩展对象上的所有 RLS 策略和安全标签都会包含在 pg_dump 创建的转储中。
扩展机制还提供了用于打包修改脚本的支持,以便调整扩展中所含 SQL 对象的定义。例如,如果扩展 1.1 版相比 1.0 版增加了一个函数,并修改了另一个函数的函数体,那么扩展作者可以提供一个更新脚本来只完成这两项修改。随后就可以使用
ALTER EXTENSION UPDATE 命令应用这些修改,并跟踪在某个数据库中实际安装的是该扩展的哪个版本。
哪些 SQL 对象种类可以成为扩展成员,见 ALTER EXTENSION 的说明。特别是,数据库集簇范围内的对象,如数据库、角色和表空间,不能成为扩展成员,因为扩展只在单个数据库内可见。(尽管并不禁止扩展脚本创建这类对象,但如果这样做,它们不会作为扩展的一部分受到跟踪。)还要注意,虽然表可以成为扩展成员,但其附属对象(如索引)并不直接被视为扩展成员。另一个重要点是,模式可以属于扩展,但反过来不成立:扩展本身只有一个非限定名,并不“位于”任何模式中。不过,扩展的成员对象会在其对象类型适用的情况下属于某个模式。扩展是否应当拥有其成员对象所在的模式,则要视具体情况而定。
如果扩展脚本创建了任何临时对象(如临时表),这些对象在当前会话的剩余时间里会被视为扩展成员,但会像其他临时对象一样在会话结束时自动删除。这是“扩展成员对象不能在不删除整个扩展的情况下被删除”这一规则的一个例外。
37.17.1. 扩展文件
CREATE EXTENSION 命令依赖于每个扩展都有一个控制文件,其名称必须是扩展名加上 .control 后缀,并放在安装目录的 SHAREDIR/extension 目录中。还必须至少有一个 SQL 脚本文件,其名称遵循 的模式(例如,扩展 extension--version.sqlfoo 的 1.0 版本使用 foo--1.0.sql)。默认情况下,脚本文件也放在 SHAREDIR/extension 目录中,但控制文件可以为脚本文件指定其他目录。
扩展控制文件的格式与 postgresql.conf 文件相同,即由一组 parameter_name
= value 赋值组成,每行一条。允许空行和以 # 引入的注释。任何不是单个单词或数字的值都要记得加引号。
控制文件可以设置下列参数:
directory(string)包含扩展 SQL 脚本文件的目录。除非给出的是绝对路径,否则该名称相对于安装的
SHAREDIR目录。默认行为等价于指定directory = 'extension'。default_version(string)扩展的默认版本(即在
CREATE EXTENSION中未指定版本时将安装的版本)。虽然这个参数可以省略,但那样一来,如果没有给出VERSION选项,CREATE EXTENSION就会失败,因此一般不希望这样做。comment(string)关于扩展的注释(任意字符串)。该注释会在初次创建扩展时应用,但不会在扩展更新时应用(因为那样可能会覆盖用户后来添加的注释)。另外,也可以在脚本文件中写一个 COMMENT 命令来设置扩展注释。
encoding(string)脚本文件所使用的字符集编码。如果脚本文件包含任何非 ASCII 字符,就应指定这个参数。否则将假定这些文件使用数据库编码。
module_pathname(string)该参数的值会替换脚本文件中每次出现的
MODULE_PATHNAME。如果未设置该参数,则不会进行替换。通常会把它设置为$libdir/,然后在 C 语言函数的shared_library_nameCREATE FUNCTION命令中使用MODULE_PATHNAME,这样脚本文件就无需把共享库的名字硬编码进去。requires(string)本扩展所依赖的其他扩展名称列表,例如
requires = 'foo, bar'。这些被依赖的扩展必须先安装好,本扩展才能安装。superuser(boolean)如果该参数为
true(默认值),则只有超级用户能够创建该扩展或把它更新到新版本(但另见下面的trusted)。如果设置为false,则只要求具备执行安装脚本或更新脚本中命令所需的权限。如果脚本中的任何命令需要超级用户权限,通常都应将此项设为true。(这些命令反正也会失败,但提前报错对用户更友好。)trusted(boolean)如果将此参数设为
true(不是默认值),则允许某些非超级用户安装superuser设为true的扩展。具体而言,当前数据库上具有CREATE权限的任何人都可以安装。当执行CREATE EXTENSION的用户不是超级用户,但凭借此参数获准安装时,安装或更新脚本会以引导超级用户的身份运行,而不是调用用户的身份。如果superuser为false,则此参数不起作用。通常,对于可能允许访问原本仅限超级用户使用的能力(例如文件系统访问)的扩展,不应将此参数设为真。此外,将扩展标记为受信任的,需要投入大量额外工作来安全地编写其安装和更新脚本;参见第 37.17.6 节。relocatable(boolean)如果一个扩展在初次创建之后仍然可以把其包含的对象移动到不同的模式中,那么它就是可重定位的。默认值为
false,也就是该扩展不可重定位。详见第 37.17.2 节。schema(string)该参数只能为不可重定位扩展设置。它强制扩展装载到指定名称的模式中,而不能装载到其他模式。
schema参数只在初次创建扩展时起作用,扩展更新时不会使用。详见第 37.17.2 节。
除主控制文件
外,扩展还可以有按如下样式命名的次级控制文件:extension.control。如果提供了这些文件,它们必须位于脚本文件目录中。次级控制文件遵循与主控制文件相同的格式。在安装或更新到该扩展的相应版本时,次级控制文件中设置的任何参数都会覆盖主控制文件中的设置。不过,extension--version.controldirectory 和 default_version
这两个参数不能在次级控制文件中设置。
扩展的 SQL 脚本文件可以包含任何 SQL 命令,但事务控制命令(BEGIN、COMMIT 等)和无法在事务块内执行的命令(例如 VACUUM)除外。这是因为脚本文件会被隐式地放在事务块中执行。
扩展的 SQL 脚本文件也可以包含以
\echo 开头的行,扩展机制会忽略这些行(将其视为注释)。这一约定通常用于在脚本文件被直接交给 psql
而不是通过 CREATE EXTENSION 装载时抛出错误(见第 37.17.7 节中的示例脚本)。如果没有这种机制,用户可能会意外地把扩展内容作为“松散”对象装载,而不是作为一个扩展来装载,这种状态恢复起来会有些麻烦。
如果扩展脚本中包含字符串 @extowner@,该字符串会被替换为调用 CREATE EXTENSION 或
ALTER EXTENSION 的用户名称(会适当加上引号)。通常,这个特性被标记为受信任的扩展用来把选定对象的所有权赋给调用用户,而不是赋给引导超级用户。(不过这样做时应当谨慎。例如,把一个 C 语言函数的所有权赋给非超级用户,会为该用户创建一条权限提升路径。)
虽然脚本文件可以包含指定编码允许的任意字符,但控制文件应只包含纯
ASCII 字符,因为 PostgreSQL 无法知道控制文件采用的是什么编码。在实践中,只有当你想在扩展注释中使用非 ASCII 字符时这才会成为问题。对此推荐的做法是不要使用控制文件中的
comment 参数,而是在脚本文件中使用
COMMENT ON EXTENSION 来设置注释。
37.17.2. 扩展的可重定位性 #
用户经常希望把扩展中的对象装载到与扩展作者原先设想不同的模式中。对于这种可重定位性,支持三个级别:
完全可重定位的扩展可以在任何时候移动到另一个模式中,即使它已经被装载到数据库之后也是如此。这通过
ALTER EXTENSION SET SCHEMA命令完成,该命令会自动把所有成员对象重命名到新模式中。通常,只有当扩展对其任何对象所在模式都没有内部假设时,才有可能做到这一点。此外,扩展的对象一开始必须全部位于同一个模式中(不属于任何模式的对象,如过程语言,不算在内)。要把一个扩展标记为完全可重定位,只需在其控制文件中设置relocatable = true。扩展可能在安装期间可重定位,但安装之后不可重定位。如果扩展脚本文件需要显式引用目标模式,例如为 SQL 函数设置
search_path属性时,通常就是这种情况。对于这样的扩展,应在控制文件中设置relocatable = false,并在脚本文件中使用@extschema@来引用目标模式。在脚本执行前,该字符串的每次出现都会被替换为实际目标模式的名称。用户可以通过CREATE EXTENSION的SCHEMA选项设置目标模式。如果扩展完全不支持重定位,应在控制文件中设置
relocatable = false,并把schema设置为预定目标模式的名称。这样将阻止使用CREATE EXTENSION的SCHEMA选项,除非它指定的正是控制文件中命名的那个模式。如果扩展对模式名称有无法通过@extschema@替换解决的内部假设,通常就需要采用这种方式。在这种情况下,@extschema@替换机制仍然可用,只是由于模式名由控制文件决定,其用途比较有限。
在所有情况下,脚本文件执行时,其初始 search_path 都会指向目标模式;也就是说,CREATE EXTENSION 做的事情等价于:
SET LOCAL search_path TO @extschema@, pg_temp;
这使得脚本文件创建的对象能够进入目标模式。脚本文件当然也可以修改
search_path,但通常不建议这样做。CREATE EXTENSION 完成后,search_path 会恢复为先前的设置。
目标模式由控制文件中的 schema 参数决定(如果给出了该参数);否则由 CREATE EXTENSION 的
SCHEMA 选项决定(如果给出了该选项);否则由当前默认的对象创建模式决定(也就是调用者的 search_path
中第一个模式)。当使用控制文件中的 schema 参数时,如果目标模式尚不存在,则会自动创建;而在另外两种情况下,目标模式必须已经存在。
如果控制文件的 requires 中列出了任何前置扩展,它们的目标模式会追加到 search_path 的初始设置中,排在新扩展的目标模式之后。这样新扩展的脚本文件就可以看到这些扩展的对象。
出于安全考虑,pg_temp 在所有情况下都会自动追加到
search_path 的末尾。
虽然不可重定位扩展可以包含分布在多个模式中的对象,但通常仍然希望把所有供外部使用的对象放在单个模式中,这个模式会被视为该扩展的目标模式。这种安排与创建依赖扩展时 search_path 的默认设置配合起来会比较方便。
37.17.3. 扩展配置表 #
有些扩展包含配置表,其中存放的数据可能会在扩展安装后被用户新增或修改。通常,如果表属于某个扩展,那么 pg_dump 既不会转储该表的定义,也不会转储其内容。但这种行为对于配置表来说并不理想;用户所做的数据更改必须包含在转储中,否则在转储并恢复之后,扩展的行为就会发生变化。
为了解决这个问题,扩展脚本文件可以把它创建的某个表或序列标记为配置关系,这样 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', '');
可以用这种方式标记任意数量的表或序列。与 serial 或
bigserial 列关联的序列也可以这样标记。
当 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 才为真。
对于序列,pg_extension_config_dump 的第二个参数没有作用。
更复杂的情况,例如用户可能会修改最初提供的数据行,可以通过在配置表上创建触发器来处理,以确保被修改的行被正确标记。
你可以再次调用 pg_extension_config_dump 来修改与配置表关联的过滤条件。(这通常在扩展更新脚本中很有用。)要把某个表标记为不再是配置表,唯一的方法是通过
ALTER EXTENSION ... DROP TABLE 将它与扩展解除关联。
注意,这些表之间的外键关系会决定 pg_dump 转储它们的顺序。具体来说,pg_dump 会尝试先转储被引用表,再转储引用表。由于外键关系是在 CREATE EXTENSION 时建立的(那时数据尚未装入这些表中),因此不支持环状依赖。若存在环状依赖,数据仍会被转储出来,但该转储将无法直接恢复,需要用户介入处理。
与 serial 或 bigserial 列关联的序列需要被直接标记,才能转储它们的状态。仅仅标记它们的父关系还不足以达到这一目的。
37.17.4. 扩展更新
扩展机制的一个优点是,它为管理定义扩展对象的 SQL 命令的更新提供了便利方式。这是通过为每个已发布版本的扩展安装脚本关联一个版本名或版本号来实现的。此外,如果你希望用户能够把数据库从一个版本动态更新到下一个版本,就应提供更新脚本,以完成从一个版本切换到下一版本所需的修改。更新脚本的名称遵循如下模式:(例如,extension--old_version--target_version.sqlfoo--1.0--1.1.sql 包含把扩展
foo 的 1.0 版修改为
1.1 版所需的命令)。
在有合适更新脚本可用的前提下,ALTER EXTENSION UPDATE 命令会把已安装的扩展更新到指定的新版本。更新脚本运行在
CREATE EXTENSION 为安装脚本提供的同一环境中:尤其是,search_path 的设置方式完全相同,而且脚本创建的任何新对象都会自动加入扩展中。此外,如果脚本选择删除扩展成员对象,它们也会自动与扩展解除关联。
如果扩展有次级控制文件,那么用于更新脚本的控制参数就是与该脚本目标(新)版本相关联的那些参数。
ALTER EXTENSION 能够执行一系列更新脚本文件来完成请求的更新。例如,如果只有 foo--1.0--1.1.sql 和
foo--1.1--2.0.sql 可用,那么当当前安装的是
1.0,而请求更新到 2.0 时,ALTER EXTENSION 就会按顺序应用这两个脚本。
PostgreSQL 并不对版本名称的属性作任何假设:例如,它并不知道 1.1 是否跟在
1.0 之后。它只是匹配可用的版本名,并选择需要应用更新脚本最少的那条路径。(实际上,版本名可以是任何不包含
--,且不以 - 开头或结尾的字符串。)
有时提供“降级”脚本也是有用的,例如
foo--1.1--1.0.sql 可用于回退与
1.1 版相关的修改。如果你这样做,要注意某个降级脚本可能会因为产生更短的路径而意外被采用。危险情况是,存在一个跨越多个版本的“快速路径”更新脚本,同时又有一个可降级到该快速路径起点的脚本。这时,先降级再走快速路径,可能比按版本逐步前进所需的步数更少。如果降级脚本删除了任何不可替代的对象,就会产生不希望看到的结果。
要检查是否存在意外的更新路径,可使用以下命令:
SELECT * FROM pg_extension_update_paths('extension_name');
它会显示指定扩展的每一对不同已知版本名,以及从源版本到目标版本将采用的更新路径序列;如果没有可用的更新路径,则显示 NULL。路径会以文本形式显示,并使用 -- 作为分隔符。如果你更喜欢数组形式,可以使用
regexp_split_to_array(path,'--')。
37.17.5. 使用更新脚本安装扩展
一个存在已久的扩展很可能会有多个版本,因此作者需要为其编写更新脚本。例如,如果你发布过 foo 扩展的
1.0、1.1 和
1.2 版本,那么就应当有更新脚本
foo--1.0--1.1.sql 和
foo--1.1--1.2.sql。在
PostgreSQL 10 之前,还必须额外创建新的脚本文件 foo--1.1.sql 和
foo--1.2.sql,用来直接构建更新后的扩展版本;否则新版本无法被直接安装,只能先安装 1.0 再升级。那样既繁琐又重复,但现在已经没有必要了,因为
CREATE EXTENSION 可以自动沿着更新链前进。例如,如果只有 foo--1.0.sql、foo--1.0--1.1.sql 和
foo--1.1--1.2.sql 这几个脚本文件可用,那么请求安装 1.2 版时,就会按顺序运行这三个脚本。处理方式与先安装 1.0 再更新到 1.2 完全相同。(和 ALTER EXTENSION UPDATE 一样,如果有多条路径可用,则优先选择最短的一条。)按这种风格组织扩展的脚本文件,可以减少制作小型更新所需的维护工作量。
如果你对以这种风格维护的扩展使用次级(特定于版本的)控制文件,请记住:即使某个版本没有独立的安装脚本,它也仍然需要一个控制文件,因为该控制文件将决定如何执行到该版本的隐式更新。例如,如果
foo--1.0.control 指定了
requires = 'bar',而 foo 的其他控制文件没有指定,那么当从 1.0 更新到其他版本时,该扩展对 bar 的依赖就会被去掉。
37.17.6. 扩展的安全注意事项 #
广泛分发的扩展应尽量少假定其所处数据库的环境。因此,以一种不会被基于搜索路径的攻击破坏的安全风格来编写扩展所提供的函数,是合适的做法。
superuser 属性设为真的扩展,还必须考虑其安装和更新脚本所执行操作的安全风险。恶意用户可以创建木马对象,破坏以后对编写不慎的扩展脚本的执行,从而获得超级用户权限;这并非特别困难。
如果扩展被标记为 trusted,那么安装用户可以选择其安装模式,而该用户可能故意使用不安全的模式,试图获得超级用户权限。因此,从安全角度看,受信任的扩展面临的风险极高,必须仔细检查其全部脚本命令,确保无法被攻破。
关于如何安全地编写函数,建议见下面的第 37.17.6.1 节;关于如何安全地编写安装脚本,建议见第 37.17.6.2 节。
37.17.6.1. 扩展函数的安全注意事项 #
扩展提供的 SQL 语言函数和 PL 语言函数在执行时会面临基于搜索路径的攻击风险,因为这些函数是在执行时而不是创建时解析的。
CREATE
FUNCTION 参考页中包含了关于如何安全编写
SECURITY DEFINER 函数的建议。对于扩展提供的任意函数,都最好应用这些技术,因为该函数可能会被高权限用户调用。
如果你无法把 search_path 设置为只包含安全模式,那就应假定每个非限定名都可能被解析为恶意用户定义的对象。要警惕那些会隐式依赖 search_path 的构造;例如,IN 和
CASE
总是通过搜索路径来选择操作符。应改用
expression WHENOPERATOR(
和 schema.=) ANYCASE WHEN 。
expression
通用扩展通常不应假定它被安装到了安全模式中,这意味着即使对其自身对象使用了模式限定引用,也并非完全没有风险。例如,如果扩展定义了函数
myschema.myfunc(bigint),那么像
myschema.myfunc(42) 这样的调用,就可能被恶意函数
myschema.myfunc(integer) 截获。要注意函数和操作符所传参数的数据类型必须与声明的参数类型精确匹配,必要时请使用显式类型转换。
37.17.6.2. 扩展脚本的安全注意事项 #
编写扩展安装脚本或更新脚本时,应防范脚本执行过程中发生基于搜索路径的攻击。如果脚本中的对象引用可能被解析为脚本作者原本无意引用的其他对象,那么破坏既可能立刻发生,也可能在稍后使用这个定义错误的扩展对象时才发生。
像 CREATE FUNCTION 和
CREATE OPERATOR CLASS 这样的 DDL 命令通常是安全的,但要警惕任何把通用表达式作为组成部分的命令。例如,CREATE VIEW 就需要仔细审查,CREATE FUNCTION 中的
DEFAULT 表达式也是如此。
有时扩展脚本可能需要执行通用 SQL,例如为完成 DDL 无法做到的系统目录调整。执行这类命令时,要确保使用安全的
search_path;不要相信
CREATE/ALTER EXTENSION 提供的路径一定是安全的。最佳做法是临时把 search_path 设为
pg_catalog, pg_temp,并在需要时显式插入对扩展安装模式的引用。(这种做法对创建视图也可能有帮助。)在
PostgreSQL 源代码发行包的
contrib 模块中可以找到示例。
跨扩展引用要做到完全安全是极其困难的,部分原因是无法确定另一个扩展位于哪个模式中。如果两个扩展都安装在同一个模式中,风险就会降低,因为这样恶意对象就无法在安装时的 search_path 中被放到被引用扩展之前。但目前还没有机制来强制要求这一点。就目前而言,最佳实践是:如果一个扩展依赖于另一个扩展,就不要将它标记为受信任的,除非那个被依赖的扩展始终安装在 pg_catalog 中。
37.17.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 FUNCTION pair(text, text)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::@extschema@.pair;';
CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, FUNCTION = pair);
-- "SET search_path" is easy to get right, but qualified names perform better.
CREATE FUNCTION lower(pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW(lower($1.k), lower($1.v))::@extschema@.pair;'
SET search_path = pg_temp;
CREATE 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)::@extschema@.pair;';
控制文件 pair.control 如下所示:
# pair extension comment = 'A key/value pair data type' default_version = '1.0' # cannot be relocatable because of use of @extschema@ relocatable = false
虽然你几乎不需要专门写一个 makefile 来把这两个文件安装到正确的目录中,但你也可以使用如下内容的 Makefile:
EXTENSION = pair DATA = pair--1.0.sql PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS)
这个 makefile 依赖于 PGXS,其说明见第 37.18 节。执行 make install
命令会把控制文件和脚本文件安装到 pg_config
报告的正确目录中。
安装文件后,使用 CREATE EXTENSION 命令将这些对象装入某个具体数据库。