PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

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 函数被包装在一个子例程中,您放在其中的 任何命名子例程都会成为嵌套的。一般而言,创建匿名子例程并通过代码 引用调用要安全得多。更多信息请参见 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

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

如果把一个 SQL 空值传给函数, 参数值在 Perl 中会表现为 “undefined”。上面 的函数定义对空输入的行为不太好(实际上,它会表现得好像它们是 零)。我们可以在函数定义中加入 STRICT,让 PostgreSQL 做更合理的事情: 如果传入空值,函数根本不会被调用,而是自动返回一个空结果。 或者,我们可以在函数体内检查 undefined 输入。 例如,假设我们希望 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 空值,返回一个 undefined 值即可。无论函数是否严格都可以这样做。

函数参数中任何不是引用的东西都是一个字符串,采用相关数据类型标准的 PostgreSQL 外部文本表示。 对于普通的数值或文本类型,Perl 会做正确的事,程序员通常无需 担心。但在其他情况下,参数需要被转换成在 Perl 中更可用的形式。 例如,decode_bytea 函数可用于把 bytea 类型的参数转换为未转义的 二进制数据。

同样,传回给 PostgreSQL 的值必须采用外部 文本表示格式。例如, encode_bytea 函数可用于为 bytea 类型的返回值转义二进制数据。

布尔值是一个特别重要的情况。 如前所述,bool 值的默认行为是以文本形式传给 Perl,因此要么是 't',要么是 'f'。这有问题,因为 Perl 不会把 'f' 当作假!可以通过使用 “transform”(见 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;

应用这个转换之后,bool 参数在 Perl 眼中会是 1 或空,从而恰当地表示真或假。 如果函数结果的类型是 bool,则根据 Perl 是否把返回值 求值为真而得到真或假。 函数内部执行的 SPI 查询的布尔查询参数和结果也会执行类似的 转换(Section 43.3.1)。

Perl 可以把对 Perl 数组的引用作为 PostgreSQL 数组返回。 例如:

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 对象传入。 该对象可以当作数组引用或字符串处理,这让为低于 PostgreSQL 9.1 的版本编写的 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 pragma,有 几个选项。对于临时的全局使用,您可以把 plperl.use_strict SET 为 true。 这会影响后续对 PL/Perl 函数的编译,但不影响当前会话中已经编译的函数。 对于永久的全局使用,您可以在 postgresql.conf 文件中把 plperl.use_strict 设为 true。

对于在特定函数中的永久使用,您可以简单地写:

use strict;

放在函数体的顶部。

如果您的 Perl 是 5.10.0 或更高版本,feature pragma 也可以 use。