受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

Chapter 43. PL/Perl — Perl 过程语言

PL/Perl 是一种可载入的过程语言,它允许使用 Perl 编程语言编写 PostgreSQL 函数和过程。

使用 PL/Perl 的主要优势在于,它允许在存储函数和过程中使用 Perl 提供的 大量字符串处理操作符和函数。与 PL/pgSQL 提供的字符串函数 和控制结构相比,使用 Perl 解析复杂字符串可能更容易。

要在特定数据库中安装 PL/Perl,可使用 CREATE EXTENSION plperl

Tip

如果把该语言安装到 template1 中,之后创建的所有 数据库都会自动安装该语言。

Note

使用源码包的用户必须在安装过程中专门启用 PL/Perl 的构建(更多信息见 Chapter 17)。使用二进制包的用户可能会在单独的 子包中找到 PL/Perl。

43.1. PL/Perl 函数和参数 #

要用 PL/Perl 语言创建一个函数,可使用标准的 CREATE FUNCTION语法:

CREATE FUNCTION funcname (argument-types)
RETURNS return-type
-- function attributes can go here
AS $$
    # PL/Perl function body goes here
$$ LANGUAGE plperl;

函数的主体就是普通的 Perl 代码。事实上,PL/Perl 的粘合代码会把它 包裹在一个 Perl 子程序中。PL/Perl 函数在标量上下文中被调用,因此 不能返回列表。如下文所述,可以通过返回引用来返回 非标量值(数组、记录和集合)。

在 PL/Perl 过程中,Perl 代码返回的任何值都会被忽略。

PL/Perl 也支持用 DO 语句调用的匿名代码块:

DO $$
    # PL/Perl code
$$ LANGUAGE plperl;

匿名代码块不接收参数,它可能返回的任何值都会被丢弃。除此之外, 它的行为与函数完全相同。

Note

在 Perl 中使用命名嵌套子程序是有危险的,特别是当它们引用外层作用域 中的词法变量时。因为 PL/Perl 函数被包装成一个子程序,任何放在其中的 命名子程序都会成为嵌套子程序。一般来说,创建通过代码引用 (coderef)调用的匿名子程序会安全得多。更多信息可见 perldiag手册页 中的Variable "%s" will not stay shared以及 Variable "%s" is not available,或者在互联网上 搜索perl nested named subroutine

CREATE FUNCTION 命令的语法要求函数体写成一个 字符串常量。通常对该字符串常量使用美元引用(见 Section 4.1.2.4)最方便。如果选择使用转义 字符串语法 E'',则必须将函数体中用到的单引号 (')和反斜线(\)都写成双份(见 Section 4.1.2.1)。

参数和结果的处理与其他 Perl 子程序一样:参数通过 @_ 传入,结果值通过 return 返回,或者作为函数中最后一个被求值的表达式返回。

例如,一个返回两个整数值中较大值的函数可以定义为:

CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
    if ($_[0] > $_[1]) { return $_[0]; }
    return $_[1];
$$ LANGUAGE plperl;

Note

参数会从数据库编码转换为 PL/Perl 中使用的 UTF-8,返回时再从 UTF-8 转回数据库编码。

如果一个 SQL 空值被传给一个函数,在 Perl 中该参数值将呈现为undefined。上述函数定义对于 空输入的行为并不理想(实际上,它会把它们当作零)。我们可以在函数 定义中添加 STRICT,让 PostgreSQL 采取更合理的做法:如果传入空值, 函数将根本不会被调用,而是自动返回空结果。另一种方式是在函数体中 检查未定义输入。例如,假设我们希望在 perl_max 的两个参数中一个为空、另一个非空时, 返回非空参数而不是空值:

CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
    my ($x, $y) = @_;
    if (not defined $x) {
        return undef if not defined $y;
        return $y;
    }
    return $x if not defined $y;
    return $x if $x > $y;
    return $y;
$$ LANGUAGE plperl;

如上所示,要从 PL/Perl 函数返回 SQL 空值,就返回一个未定义值。 无论函数是否为严格函数,都可以这样做。

函数参数中任何不是引用的值都是字符串,其格式是相应数据类型的标准 PostgreSQL 外部文本表示。对于普通的数值或 文本类型,Perl 通常会正确处理,程序员一般不必操心。不过在其他情况下, 参数可能需要转换成 Perl 中更易用的形式。例如,可以用 decode_bytea 函数把 bytea 类型的 参数转换成未转义的二进制数据。

同样,返回给 PostgreSQL 的值也必须采用 外部文本表示格式。例如,可以用 encode_bytea 函数把二进制数据转义成适合作为 bytea 类型返回值的形式。

有一种特别重要的情况是布尔值。如前所述,bool 值默认会以 文本形式传给 Perl,因此要么是 't',要么是 'f'。这会带来问题,因为 Perl 不会把 'f' 当成假值!可以通过使用转换 来改进这一点(见 CREATE TRANSFORM)。 bool_plperl 扩展提供了合适的转换。要使用它, 请先安装该扩展:

CREATE EXTENSION bool_plperl;  -- or bool_plperlu for PL/PerlU

然后对接收或返回 bool 的 PL/Perl 函数使用 TRANSFORM 函数属性,例如:

CREATE FUNCTION perl_and(bool, bool) RETURNS bool
TRANSFORM FOR TYPE bool
AS $$
  my ($a, $b) = @_;
  return $a && $b;
$$ LANGUAGE plperl;

应用该转换后,Perl 会将 bool 参数视为 1 或空值,从而能正确表示真或假。如果函数结果类型为 bool,则其真或假取决于 Perl 是否会将返回值求值为真。 对函数内部执行的 SPI 查询,其布尔查询参数和结果也会进行类似的转换 (Section 43.3.1)。

Perl 可以把 PostgreSQL 数组返回为对 Perl 数组的引用。这里有一个示例:

CREATE OR REPLACE function returns_array()
RETURNS text[][] AS $$
    return [['a"b','c,d'],['e\\f','g']];
$$ LANGUAGE plperl;

select returns_array();

Perl 把 PostgreSQL 数组作为一个经 bless 的 PostgreSQL::InServer::ARRAY 对象传递。该对象既可以当作 数组引用,也可以当作字符串处理,从而可以运行为 9.1 之前版本的 PostgreSQL 编写的 Perl 代码,以保持向后兼容性。 例如:

CREATE OR REPLACE FUNCTION concat_array_elements(text[]) RETURNS TEXT AS $$
    my $arg = shift;
    my $result = "";
    return undef if (!defined $arg);

    # as an array reference
    for (@$arg) {
        $result .= $_;
    }

    # also works as a string
    $result .= $arg;

    return $result;
$$ LANGUAGE plperl;

SELECT concat_array_elements(ARRAY['PL','/','Perl']);

Note

多维数组会以所有 Perl 程序员都熟悉的方式,表示为对低维数组引用的引用。

复合类型参数被作为哈希的引用传递给函数。哈希的键是复合类型的 属性名。这里是一个示例:

CREATE TABLE employee (
    name text,
    basesalary integer,
    bonus integer
);

CREATE FUNCTION empcomp(employee) RETURNS integer AS $$
    my ($emp) = @_;
    return $emp->{basesalary} + $emp->{bonus};
$$ LANGUAGE plperl;

SELECT name, empcomp(employee.*) FROM employee;

PL/Perl 函数也可以用相同的方法返回复合类型:返回一个具有所需属性的 哈希的引用。例如:

CREATE TYPE testrowperl AS (f1 integer, f2 text, f3 text);

CREATE OR REPLACE FUNCTION perl_row() RETURNS testrowperl AS $$
    return {f2 => 'hello', f1 => 1, f3 => 'world'};
$$ LANGUAGE plperl;

SELECT * FROM perl_row();

在声明的结果数据类型中,凡是哈希中未出现的列都将作为空值返回。

同样,过程的输出参数也可以作为哈希引用返回:

CREATE PROCEDURE perl_triple(INOUT a integer, INOUT b integer) AS $$
    my ($a, $b) = @_;
    return {a => $a * 3, b => $b * 3};
$$ LANGUAGE plperl;

CALL perl_triple(5, 10);

PL/Perl 函数还可以返回标量类型或复合类型的集合。通常会希望逐行返回, 这既能加快启动时间,也能避免把整个结果集排入内存。如下所示, 可以用 return_next 实现这一点。注意,在最后一次 return_next 之后,必须写上 return 或者(更好)return undef

CREATE OR REPLACE FUNCTION perl_set_int(int)
RETURNS SETOF INTEGER AS $$
    foreach (0..$_[0]) {
        return_next($_);
    }
    return undef;
$$ LANGUAGE plperl;

SELECT * FROM perl_set_int(5);

CREATE OR REPLACE FUNCTION perl_set()
RETURNS SETOF testrowperl AS $$
    return_next({ f1 => 1, f2 => 'Hello', f3 => 'World' });
    return_next({ f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL' });
    return_next({ f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' });
    return undef;
$$ LANGUAGE plperl;

对于较小的结果集,可以返回一个数组引用,其中对简单类型、数组类型 和复合类型分别包含标量、数组引用或哈希引用。下面是把整个结果集 作为数组引用返回的几个简单示例:

CREATE OR REPLACE FUNCTION perl_set_int(int) RETURNS SETOF INTEGER AS $$
    return [0..$_[0]];
$$ LANGUAGE plperl;

SELECT * FROM perl_set_int(5);

CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
    return [
        { f1 => 1, f2 => 'Hello', f3 => 'World' },
        { f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL' },
        { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' }
    ];
$$ LANGUAGE plperl;

SELECT * FROM perl_set();

如果希望在代码中使用 strict 编译指示,可以有几种 选择。对于临时的全局用法,可以 SET plperl.use_strict 为 true。这会影响后续编译的 PL/Perl 函数,但不会影响当前会话中已经编译的 函数。对于永久的全局用法,可以在 postgresql.conf 文件中将 plperl.use_strict 设为 true。

如果要在特定函数中永久启用它,可以简单地把

use strict;

放在函数体顶部。

如果 Perl 版本为 5.10.0 或更高,也可以通过 use 使用 feature 编译指示。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。