pg_variables: 各种类型的会话级变量

三月 5, 2024

摘要pg_variables模块提供了处理各种类型变量的函数。创建的变量仅在当前用户会话中可用。

本文包含以下部分:

  1. 介绍
  2. 安装
  3. 模块函数
  4. 示例

介绍

pg_variables 模块提供了处理各种类型变量的函数。创建的变量仅存在于当前用户会话中。默认情况下,创建的变量不是事务性的(即它们不受BEGINCOMMITROLLBACK语句的影响)。然而,这是可以通过pgv_set()的参数is_transactional,进行定制的行为:

SELECT pgv_set('vars', 'int1', 101);
BEGIN;
SELECT pgv_set('vars', 'int2', 102);
ROLLBACK;

SELECT * FROM pgv_list() order by package, name;
 package | name | is_transactional
---------+------+------------------
 vars    | int1 | f
 vars    | int2 | f

但如果是指定标记 is_transactional 创建的变量:

BEGIN;
SELECT pgv_set('vars', 'trans_int', 101, true);
SAVEPOINT sp1;
SELECT pgv_set('vars', 'trans_int', 102, true);
ROLLBACK TO sp1;
COMMIT;
SELECT pgv_get('vars', 'trans_int', NULL::int);
 pgv_get
---------
     101

您可以将变量聚集到包中。这样做是为了能够让变量具有不同的名称,或者快速删除整批变量。如果包变空了,则会自动删除。

安装

典型的安装过程可能如下所示:

$ cd pg_variables
$ make USE_PGXS=1
$ sudo make USE_PGXS=1 install
$ make USE_PGXS=1 installcheck
$ psql DB -c "CREATE EXTENSION pg_variables;"

模块函数

pg_variables 模块提供的函数如下表所示。

要使用 pgv_get() 函数,需要包和变量已经存在。要使用 pgv_get() 函数,必须使用 pgv_set() 函数设置变量。

如果一个包不存在,你会得到如下错误:

SELECT pgv_get('vars', 'int1', NULL::int);
ERROR:  unrecognized package "vars"

如果一个变量不存在,你会得到如下错误:

SELECT pgv_get('vars', 'int1', NULL::int);
ERROR:  unrecognized variable "int1"

pgv_get() 函数会检查变量类型。如果变量类型与函数类型不匹配,则会引发错误:

SELECT pgv_get('vars', 'int1', NULL::text);
ERROR:  variable "int1" requires "integer" value

标量变量函数

函数 返回类型
pgv_set(package text, name text, value anynonarray, is_transactional bool default false) void
pgv_get(package text, name text, var_type anynonarray, strict bool default true) anynonarray

数组变量函数

函数 返回类型
pgv_set(package text, name text, value anyarray, is_transactional bool default false) void
pgv_get(package text, name text, var_type anyarray, strict bool default true) anyarray

pgv_set参数:

  • package - 包的名称,如果不存在,会创建。
  • name - 变量的名称,如果不存在,会创建该变量。如果变量已经存在,并且它的事务属性和is_transactional参数不匹配 ,则pgv_set会失败。
  • value - 变量的新值。如果变量已经存在,并且其类型与新值的类型不匹配,则pgv_set会失败。
  • is_transactional - 新创建的变量的事务属性,默认为 false。

pgv_get参数:

  • package - 现有包的名称。如果包不存在,则结果取决于strict参数:如果它为 false,则pgv_get返回 NULL,否则会失败。
  • name - 现有变量的名称。如果变量不存在,则结果取决于strict参数:如果它为 false,则pgv_get返回 NULL,否则会失败。
  • var_type - 现有变量的类型。需要传递它以获得正确的返回类型。
  • strict - 如果之前没有创建变量或包的情况下,需要pgv_get不引发错误,则传递 false,默认情况下为 true。

记录变量函数

模块提供了以下函数,来处理一系列记录类型。

要使用 pgv_update()pgv_delete()pgv_select() 函数,必须存在包和变量。否则会引发错误。需要使用 pgv_insert() 函数设置变量,才能使用这些函数。

pgv_update()pgv_delete()pgv_select() 函数会检查变量类型。如果变量类型不匹配记录类型,则会引发错误。

函数 返回类型 描述
pgv_insert(package text, name text, r record, is_transactional bool default false) void 将一条记录添加到变量集合中。如果包和变量不存在,它们将被创建。r 的第一列将是一个主键。如果存在具有相同主键的记录,则会引发错误。如果此变量集合具有其他结构,则会引发错误。
pgv_update(package text, name text, r record) boolean 使用相应的主键更新记录(r 的第一列是主键)。如果找到记录,则返回 true。如果此变量集合具有其他结构,则会引发错误。
pgv_delete(package text, name text, value anynonarray) boolean 使用相应的主键来删除一条记录(r 的第一列是主键)。如果找到记录,则返回 true
pgv_select(package text, name text) set of record 返回变量集合中的记录。
pgv_select(package text, name text, value anynonarray) record 返回带有相应主键的记录(r 的第一列是主键)。
pgv_select(package text, name text, value anyarray) set of record 返回带有相应主键的变量集合中的记录(r 的第一列是主键)。

辅助函数

函数 返回类型 描述
pgv_exists(package text, name text) bool 如果包和变量存在,则返回 true
pgv_exists(package text) bool 如果包存在,则返回 true
pgv_remove(package text, name text) void 使用相应的名称删除变量。所需的包和变量必须存在,否则将引发错误。
pgv_remove(package text) void 使用相应的名称删除包和所有包变量。所需的包必须存在,否则将引发错误。
pgv_free() void 删除所有包和变量。
pgv_list() table(package text, name text, is_transactional bool) 返回已定义的包和变量的记录集。
pgv_stats() table(package text, allocated_memory bigint) 返回已定义的包的列表,和占用的内存(以字节为单位)。

请注意,pgv_stats() 仅适用于 PostgreSQL 9.6 及更高版本。

示例

使用函数来处理标量和数组变量是很容易的:

SELECT pgv_set('vars', 'int1', 101);
SELECT pgv_set('vars', 'text1', 'text variable'::text);

SELECT pgv_get('vars', 'int1', NULL::int);
 pgv_get_int
-------------
         101

SELECT SELECT pgv_get('vars', 'text1', NULL::text);
    pgv_get
---------------
 text variable

SELECT pgv_set('vars', 'arr1', '{101,102}'::int[]);

SELECT pgv_get('vars', 'arr1', NULL::int[]);
  pgv_get
-----------
 {101,102}

假设我们有一个 tab 表:

CREATE TABLE tab (id int, t varchar);
INSERT INTO tab VALUES (0, 'str00'), (1, 'str11');

然后你可以使用函数来处理记录变量:

SELECT pgv_insert('vars', 'r1', tab) FROM tab;

SELECT pgv_select('vars', 'r1');
 pgv_select
------------
 (1,str11)
 (0,str00)

SELECT pgv_select('vars', 'r1', 1);
 pgv_select
------------
 (1,str11)

SELECT pgv_select('vars', 'r1', 0);
 pgv_select
------------
 (0,str00)

SELECT pgv_select('vars', 'r1', ARRAY[1, 0]);
 pgv_select
------------
 (1,str11)
 (0,str00)

SELECT pgv_delete('vars', 'r1', 1);

SELECT pgv_select('vars', 'r1');
 pgv_select
------------
 (0,str00)

您可以列出包和变量:

SELECT * FROM pgv_list() order by package, name;
 package | name  | is_transactional
---------+-------+------------------
 vars    | arr1  | f
 vars    | int1  | f
 vars    | r1    | f
 vars    | text1 | f

并获取已占用的内存(以字节为单位):

SELECT * FROM pgv_stats() order by package;
 package | allocated_memory
---------+------------------
 vars    |            49152

您可以删除变量或整个包:

SELECT pgv_remove('vars', 'int1');
SELECT pgv_remove('vars');

您可以删除所有包和变量:

SELECT pgv_free();

如果你想要支持事务和保存点的变量,你应该在函数pgv_set()pgv_insert()的最后一个参数,带上标记is_transactional = true。以下用例描述了事务型变量的行为:

SELECT pgv_set('pack', 'var_text', 'before transaction block'::text, true);
BEGIN;
SELECT pgv_set('pack', 'var_text', 'before savepoint'::text, true);
SAVEPOINT sp1;
SELECT pgv_set('pack', 'var_text', 'savepoint sp1'::text, true);
SAVEPOINT sp2;
SELECT pgv_set('pack', 'var_text', 'savepoint sp2'::text, true);
RELEASE sp2;
SELECT pgv_get('pack', 'var_text', NULL::text);
    pgv_get
---------------
 savepoint sp2

ROLLBACK TO sp1;
SELECT pgv_get('pack', 'var_text', NULL::text);
     pgv_get
------------------
 before savepoint

ROLLBACK;
SELECT pgv_get('pack', 'var_text', NULL::text);
         pgv_get
--------------------------
 before transaction block

如果您在BEGINSAVEPOINT语句之后创建了一个事务型变量,然后回滚到前面的状态 - 变量将不再存在:

BEGIN;
SAVEPOINT sp1;
SAVEPOINT sp2;
SELECT pgv_set('pack', 'var_int', 122, true);
RELEASE SAVEPOINT sp2;
SELECT pgv_get('pack', 'var_int', NULL::int);
pgv_get
---------
     122

ROLLBACK TO sp1;
SELECT pgv_get('pack','var_int', NULL::int);
ERROR:  unrecognized variable "var_int"
COMMIT;

您可以通过ROLLBACK撤消删除事务型变量,但如果您删除整个包,则所有常规变量会被永久删除:

SELECT pgv_set('pack', 'var_reg', 123);
SELECT pgv_set('pack', 'var_trans', 456, true);
BEGIN;
SELECT pgv_free();
SELECT * FROM pgv_list();
 package | name | is_transactional
---------+------+------------------
(0 rows)

-- Memory is allocated yet
SELECT * FROM pgv_stats();
 package | allocated_memory
---------+------------------
 pack    |            24576

ROLLBACK;
SELECT * FROM pgv_list();
 package |   name    | is_transactional
---------+-----------+------------------
 pack    | var_trans | t

一旦您创建了一个事务型变量,则每次要通过函数pgv_set()pgv_insert()更改变量值时,都应使用标记is_transactional。如果您尝试变更此事务型标记,您会得到一个错误:

SELECT pgv_insert('pack', 'var_record', row(123::int, 'text'::text), true);

SELECT pgv_insert('pack', 'var_record', row(456::int, 'another text'::text));
ERROR:  variable "var_record" already created as TRANSACTIONAL

函数pgv_update()pgv_delete()不需要此标记。