为什么这个Postgres存储过程要使用`utf8`?

5

我在使用Postgres 9.2和Perl 5.12.4中的plperl存储过程时发现了一个奇怪的问题。

这种奇怪的行为可以使用以下“错误”的存储过程进行复现:

CREATE FUNCTION foo(VARCHAR) RETURNS VARCHAR AS $$
    my ( $re ) = @_;
    $re = ''.qr/\b($re)\b/i;
    return $re;
$$ LANGUAGE plperl;

执行时:

# select foo('foo');
ERROR:  Unable to load utf8.pm into plperl at line 3.
BEGIN failed--compilation aborted.
CONTEXT:  PL/Perl function "foo"

然而,如果我将qr//操作移到eval语句中,它就可以工作:
CREATE OR REPLACE FUNCTION bar(VARCHAR) RETURNS VARCHAR AS $$
    my ( $re ) = @_;
    eval "\$re = ''.qr/\\b($re)\\b/i;";
    return $re;
$$ LANGUAGE plperl;

结果:

# select bar('foo');
       bar       
-----------------
 (?^i:\b(foo)\b)
(1 row)
  1. 为什么 eval 会绕过自动的 use utf8

  2. 为什么首先需要使用 use utf8?我的代码不是 UTF8 格式,据说只有在这种情况下才需要使用 use utf8

    如果有什么问题,我可能希望 eval 版本在没有 use utf8 的情况下出现错误,比如输入脚本包含非 ASCII 值的情况。(进一步测试表明将非 ASCII 值传递给 bar() 确实会导致 eval 失败并显示相同的错误)


请注意,许多 Postgres 安装程序会在 perl 解释器启动时自动加载 'utf8'。至少在 Debian 中,通过执行 DO 'elog(WARNING, join ", ", sort keys %INC)' language plperl; 可以证明这一点:

WARNING: Carp.pm, Carp/Heavy.pm, Exporter.pm, feature.pm, overload.pm, strict.pm, unicore/Heavy.pl, unicore/To/Fold.pl, unicore/lib/Perl/SpacePer.pl, utf8.pm, utf8_heavy.pl, vars.pm, warnings.pm, warnings/register.pm
CONTEXT: PL/Perl anonymous code block
DO

但在表现奇怪的机器上并非如此:

WARNING: Carp.pm, Carp/Heavy.pm, Exporter.pm, feature.pm, overload.pm, overloading.pm, strict.pm, vars.pm, warnings.pm, warnings/register.pm
CONTEXT: PL/Perl anonymous code block
DO

这个问题不是关于如何让我的目标机器自动加载 utf8;我知道如何做。我想知道为什么似乎首先需要这样做。


由于您忘记转义\,因此无法正常工作。您还应该转义第二个$ - ikegami
@ikegami:第二个 $ 不应该被转义,但是对于 \ 字符的好召唤;现在我得到了正确的输出,这使问题更加神秘。 - Jonathan Hall
是的,应该这样做。如果您不转义第二个 $,则会在 $re='/; system("rm -rf /"); qr/'; 中失败。注入错误! - ikegami
@ikegami:确实;为了测试的目的,可以忽略那种情况。虽然转义 $ 也会导致 eval 以同样的方式失败。嗯。 - Jonathan Hall
@ikegami:这也会导致它死亡,并显示“无法加载utf8.pm”的错误。看起来Perl对传递给qr//eval()的字符串类型非常聪明。默认情况下,$re是一个Unicode字符串(按照Perl的默认设置),因此在执行qr时会相应地表现。 - Jonathan Hall
2个回答

4
在失败的版本中,您正在执行
$re = ''.qr/\b($re)\b/i

在后续版本中,您正在执行。
$re = ''.qr/\b(foo)\b/i

听起来像qr//在编译为Unicode模式的情况下需要utf8.pm,但是后者并没有编译为Unicode模式。


加载utf8.pm失败是由plperl创建的Safe隔离环境所限制造成的。

解决方法是在Safe隔离环境之外加载该模块。

解决办法是使用更高效的

$re = '(?^u:\\b(?i:'.$re.')\\b)';

我认为你回答了这个问题;尽管你的解决方法并不是真正的解决方法,因为它并没有做同样的事情。 - Jonathan Hall
不管是 $re=qr/foo/i; 还是 $re='/foo/i'; 都是一样的。(不确定你指的是哪一个)。唯一想到的区别是,如果 $re 中的模式非法,它不会死亡。 - ikegami
我会进一步调查qr。谢谢。 - Jonathan Hall

2

我曾经遇到过同样的问题,后来通过添加以下代码解决了这个问题

plperl.on_init = 'use utf8; use re; package utf8; require "utf8_heavy.pl";'

修改postgresql.conf文件。

希望这能帮到某些人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接