Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

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 glue 代码将其包装在 Perl 子例程中。PL/Perl 函数在标量上下文中调用,因此无法返回列表。您可以通过返回引用(如下所述)来返回非标量值(数组、记录和集合)。

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

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

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

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

注意

在 Perl 中使用命名的嵌套子例程很危险,尤其是在它们引用封闭作用域中的词法变量时。由于 PL/Perl 函数被包装在子例程中,因此您在其中放置的任何命名子例程都将被嵌套。通常,创建匿名子例程并通过代码引用调用它们会安全得多。有关详细信息,请参阅 perldiag 手册页中 Variable "%s" will not stay sharedVariable "%s" is not available 的条目,或者在 Internet 中搜索 perl nested named subroutine

CREATE FUNCTION 命令的语法要求将函数主体写为字符串常量。通常最方便的方法是将字符串常量用美元引用(请参阅 第 4.1.2.4 节)。如果您选择使用转义字符串语法 E'',则必须加倍函数主体中使用的任何单引号 (') 和反斜杠 (\)(请参阅 第 4.1.2.1 节)。

参数和结果的处理方式与任何其他 Perl 子例程中的相同:参数传递给 @_,并通过 return 或作为函数中计算的最后一个表达式返回结果值。

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

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

注意

参数将从数据库编码转换为 UTF-8 以便在 PL/Perl 中使用,然后在返回时从 UTF-8 转换回数据库编码。

如果将 SQL NULL 值传递给某个函数,该参数值会在 Perl 中显示为 未定义。使用上述函数定义来处理 null 输入时效果并不好(事实上,该函数会将 null 输入当作零进行处理)。我们可以在该函数定义中添加 STRICT,使 PostgreSQL 执行更合理的操作:如果传递 null 值,该函数将根本不会被调用,而是自动返回一个 null 结果。或者,我们可以在函数主体中检查未定义的输入。例如,假设我们希望 perl_max 在收到一个 null 参数和一个非 null 参数后返回非 null 参数(而不是 null 值)

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 null 值,请返回未定义的值。该操作可以在函数严格与否的情况下执行。

函数参数中不作为引用的任何内容都是字符串,字符串以适用于相关数据类型的标准 PostgreSQL 外部文本表示形式显示。如果是普通的数字或文本类型,Perl 会执行正确操作,并且程序员通常无需担心该问题。但是,在其他情况下,需要将参数转换为在 Perl 中更易于使用的一种形式。例如,可以使用 decode_bytea 函数将类型为 bytea 的参数转换为未转义的二进制文件。

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

布尔值是一个非常重要的案例。如前所述,bool 值的默认行为是将其作为文本传递给 Perl,因此是 't''f'。这一行为会带来问题,因为 Perl 不会将 'f' 视为 false!可以通过使用 转换(请参阅 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 查询的布尔查询参数和结果也是如此(第 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 数组作为 blessed 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']);

注意

多维数组表示为对多维引用数组的引用,这是对每个 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();

已声明结果数据类型中不在哈希中的任何列都将作为 NULL 值返回。

与此类似,可以将过程的输出参数作为哈希引用返回

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 则有几个选项。对于临时全局使用,您可以将 SET plperl.use_strict 设置为 true。这将影响 PL/Perl 函数的后续编译,但不影响当前会话中已编译的函数。对于永久全局使用,您可以在 postgresql.conf 文件中将 plperl.use_strict 设置为 true。

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

use strict;

放在函数正文字符的顶部。

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