介绍
最近,在 PostgreSQL 社区中,一位用户发布了一块 PL/pgSQL 代码(如下所示),惊讶地发现它没有产生语法错误:
DO $$
DECLARE i INT;
BEGIN
i = 0;
IF i = 0 AND THEN
RAISE NOTICE 'i = 0';
END IF;
END; $$;
乍一看,这个代码块似乎并不完整。请注意IF条件:它在AND运算符后面似乎缺少了一个额外的条件。从逻辑上讲,这应该会出现异常,因为AND后面的条件不完整。
IF i = 0 AND THEN
但是,在 PL/pgSQL 执行期间,条件被评估,并没有出现任何语法错误或警告。这就提出了一个关键问题:
• PostgreSQL 在内部如何处理这个条件?
• 是什么让这个看似不完整的表达式正常工作的呢?
• 条件 “i = 0 AND” 是如何被处理的?
在检视此类 PL/pgSQL 代码时,它看起来不完整,假设它应该失败的情况下,会令人惊讶。
在本文中,我们将深入研究 PL/pgSQL 的内部结构,以了解此条件语句的处理方式,以及为什么 PostgreSQL 没有将其标记为错误。
PL/pgSQL 内部的代码实现
开源软件的一个最大优势是,能够直接检查代码库。这为我们理解事物的运作方式,或在某些情况下为什么它们没有按预期中断,提供了基础。
我们的调查从PLPGSQL_STMT_IF结构开始,通过在pl_exec.c文件中的调用栈进行跟踪。
通过浏览代码,我们发现IF语句及其条件是使用exec_run_select进行求值的,它有效地执行了一条返回一个布尔结果的SELECT语句。
作为 SELECT 子句的 PL/pgSQL 条件
回顾原始的示例,条件i = 0 AND是在一个SELECT子句中处理的。在这里,AND本质上充当一个占位符,允许 PL/pgSQL 引擎在不触发语法错误的情况下计算条件。
-- the condition is evaluated as SQL,
-- "and" is treated as column alias
SELECT 0 = 0 AND;
and
-----
t
(1 row)
这种见解 — 条件表达式作为SELECT语句进行计算 — 开辟了新的可能性。这意味着,我们可以在 PL/pgSQL 的条件中利用各种函数,如以下示例所示。
以下代码片段说明了,在 PL/pgSQL 中计算条件表达式的不同方式:
条件 | SQL | PL/pgSQL |
计数求值 | select count(1) = 1; | do $$ |
不区分大小写的匹配 | select ('a' ilike 'A'); | do $$ |
复杂的 unnest – 基于数组的条件 | select COUNT(col1) filter(where col1 = 'A') = 2 from (select unnest(ARRAY['A','B','A']) col1) | do $$ |
行存在性检查 | select exists (select 1 from generate_series(1,10000)); | do $$ |
结论
PL/pgSQL 使用SELECT语句处理条件表达式,允许不完整的条件(如IF i = 0 AND)无错误地执行。
条件中的AND并没有出错,而是将其作为了SELECT表达式的一部分,让 PostgreSQL 灵活地计算它。这种方法允许开发者将各种SELECT表达式直接融入到条件计算中,从而提供了其他的方法,可在 PL/pgSQL 中无缝构建和测试复杂逻辑。