Postgresql 中文操作指南

43.12. Tips for Developing in PL/pgSQL #

使用 PL/pgSQL 开发的一种好方法是在文本编辑器中创建函数,在另一个窗口中使用 psql 来加载并测试这些函数。如果您以这种方式进行操作,则最好使用 CREATE OR REPLACE FUNCTION 编写函数。这样,您只需重新加载文件即可更新函数定义。例如:

CREATE OR REPLACE FUNCTION testfunc(integer) RETURNS integer AS $$
          ....
$$ LANGUAGE plpgsql;

在运行 psql 时,您可以使用以下命令加载或重新加载此类函数定义文件:

\i filename.sql

然后立即发出 SQL 命令来测试该函数。

使用 PL/pgSQL 开发的另一种好方法是使用 GUI 数据库访问工具,该工具有助于以过程语言进行开发。这种工具的一个例子是 pgAdmin,尽管还有其他工具。这些工具通常提供方便的功能,例如转义单引号并使其更容易重新创建和调试函数。

43.12.1. Handling of Quotation Marks #

PL/pgSQL 函数的代码指定在 _CREATE FUNCTION_中,作为字符串文本。如果你用普通方法编写字符串文本,并使用单引号将其包围,则函数体内的任何单引号都必须加倍;同样,任何反斜杠都必须加倍(假设使用了转义字符串语法)。加倍引号充其量是乏味的,而且在更复杂的情况下,代码可能会变得难以理解,因为你很容易发现自己需要六个或更多相邻的引号。建议你改为将函数体写为“美元引号”字符串文字(请参阅 Section 4.1.2.4)。在美元引用方法中,你永远不会加倍任何引号,而要仔细选择所需的每个嵌套级别不同的美元引用分隔符。例如,你可以将 _CREATE FUNCTION_命令写成:

CREATE OR REPLACE FUNCTION testfunc(integer) RETURNS integer AS $PROC$
          ....
$PROC$ LANGUAGE plpgsql;

在此,您可以在 SQL 命令中对简单的文字字符串使用引号和 _ to delimit fragments of SQL commands that you are assembling as strings. If you need to quote text that includes ,可以使用 _$Q$,依此类推。

下表显示了在不使用美元引用时编写引号时必须执行的操作。在将美元引用前的代码转换为更易理解的内容时,它可能很有用。

  • 1 quotation mark #

    • 例如,要开始和结束函数体:

CREATE FUNCTION foo() RETURNS integer AS '
          ....
' LANGUAGE plpgsql;
  • 在单引号函数体中的任何位置,引号 must 成对出现。

    • 2 quotation marks #

  • 例如,对于函数体内的字符串文字:

a_output := ''Blah'';
SELECT * FROM users WHERE f_name=''foobar'';
  • 在采用美元符号引用法时,您只需编写:

a_output := 'Blah';
SELECT * FROM users WHERE f_name='foobar';
  • 这正是 PL/pgSQL 解析器在任何情况下都会看到的内容。

    • 4 quotation marks #

  • 例如,当您需要在函数体内的字符串常量中使用单个引号时:

a_output := a_output || '' AND name LIKE ''''foobar'''' AND xyz''
  • 实际上附加到 a_output 的值将是:AND name LIKE 'foobar' AND xyz

  • 在采用美元符号引用法时,您会编写:

a_output := a_output || $$ AND name LIKE 'foobar' AND xyz$$
  • 小心引用这周围的单引号分隔符,而不仅仅是 $$

    • 6 quotation marks #

  • 例如,对于函数体内字符串中紧靠该字符串常量末尾的单引号:

a_output := a_output || '' AND name LIKE ''''foobar''''''
  • 添加到 a_output 的值将是:AND name LIKE 'foobar'

  • 在单引号方式中,这将变成:

a_output := a_output || $$ AND name LIKE 'foobar'$$
  • 10 quotation marks #

    • 当你想要在字符串常量中使用两个单引号(这占用了 8 个引号)并且它与该字符串常量的末尾相邻(再加 2 个引号)时。你可能只需要在编写生成其他函数的函数(例如 Example 43.10)时用到该特性。例如:

a_output := a_output || '' if v_'' ||
    referrer_keys.kind || '' like ''''''''''
    || referrer_keys.key_string || ''''''''''
    then return ''''''  || referrer_keys.referrer_type
    || ''''''; end if;'';
  • a_output 的值将是:

if v_... like ''...'' then return ''...''; end if;
  • 在单引号方式中,这将变成:

a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
    || referrer_keys.key_string || $$'
    then return '$$  || referrer_keys.referrer_type
    || $$'; end if;$$;
  • 其中我们假设我们只需要在 a_output 中放入单引号,因为它会在使用前重新引用。

43.12.2. Additional Compile-Time and Run-Time Checks #

为了帮助用户在问题造成损害前发现简单但常见问题的实例,PL/pgSQL 提供了其他 checks。启用后,根据配置,它们可用于在编译函数期间发出 WARNINGERROR。收到 WARNING 的函数可以在不产生进一步消息的情况下执行,因此建议您在单独的开发环境中进行测试。

在开发和/或测试环境中,适当将 plpgsql.extra_warningsplpgsql.extra_errors 设置为 "all" 是值得鼓励的。

通过配置变量 plpgsql.extra_warnings(警告)和 plpgsql.extra_errors(错误)启用这些附加检查。两者都可以设置为检查的逗号分隔列表 "none""all"。默认值为 "none"。当前,可用检查列表包括:

  • shadowed_variables #

    • 检查声明是否隐藏了以前定义的变量。

  • strict_multi_assignment #

    • 某些 PL/pgSQL 命令允许一次为多个变量分配值,例如 SELECT INTO。通常,目标变量的数量和源变量的数量应匹配,虽然 PL/pgSQL 将使用 NULL 获得缺失值,但将忽略额外的变量。启用此检查将导致 PL/pgSQL 在目标变量的数量和源变量的数量不同时抛出 WARNINGERROR

  • too_many_rows #

    • 启用此检查将导致 PL/pgSQL 检查在使用 INTO 子句时给定的查询是否返回多行。因为 INTO 语句将只使用一行,所以让查询返回多行通常是低效和/或非确定性的,因此可能是一个错误。

以下示例显示将 plpgsql.extra_warnings 设置为 shadowed_variables 的效果:

SET plpgsql.extra_warnings TO 'shadowed_variables';

CREATE FUNCTION foo(f1 int) RETURNS int AS $$
DECLARE
f1 int;
BEGIN
RETURN f1;
END;
$$ LANGUAGE plpgsql;
WARNING:  variable "f1" shadows a previously defined variable
LINE 3: f1 int;
        ^
CREATE FUNCTION

以下示例显示将 plpgsql.extra_warnings 设置为 strict_multi_assignment 的效果:

SET plpgsql.extra_warnings TO 'strict_multi_assignment';

CREATE OR REPLACE FUNCTION public.foo()
 RETURNS void
 LANGUAGE plpgsql
AS $$
DECLARE
  x int;
  y int;
BEGIN
  SELECT 1 INTO x, y;
  SELECT 1, 2 INTO x, y;
  SELECT 1, 2, 3 INTO x, y;
END;
$$;

SELECT foo();
WARNING:  number of source and target fields in assignment does not match
DETAIL:  strict_multi_assignment check of extra_warnings is active.
HINT:  Make sure the query returns the exact list of columns.
WARNING:  number of source and target fields in assignment does not match
DETAIL:  strict_multi_assignment check of extra_warnings is active.
HINT:  Make sure the query returns the exact list of columns.

 foo
-----

(1 row)