postgresql - 使用 regclass 的动态 SQL 的意外行为

标签 postgresql stored-procedures plpgsql dynamic-sql postgresql-9.3

我使用 EXECUTEregclass 时出现一些奇怪的行为,我正在尝试调试它并需要一些帮助。

基本上,我尝试在函数中运行这些 SQL 语句:

ALTER TABLE mytable_bak RENAME TO mytable_old;
TRUNCATE TABLE mytable_old;
ALTER TABLE mytable RENAME TO mytable_bak;
ALTER TABLE mytable_old RENAME TO mytable;

这是我的函数(未按预期工作):

CREATE OR REPLACE FUNCTION foo(_t regclass)
  RETURNS void AS
$func$
BEGIN
   EXECUTE 'ALTER TABLE '|| _t ||'_bak RENAME TO '|| _t || '_old';
   EXECUTE 'TRUNCATE TABLE '|| _t || '_old';
   EXECUTE 'ALTER TABLE '|| _t ||' RENAME TO '|| _t || '_bak';
   EXECUTE 'ALTER TABLE '|| _t ||'_old RENAME TO '|| _t;
END
$func$ LANGUAGE plpgsql;

当我执行时,它不喜欢最后一行:

EXECUTE 'ALTER TABLE '|| _t ||'_old RENAME TO '|| _t;

例如:

foo_bar_12345=> select foo('mytable');
ERROR:  relation "mytable_bak_old" does not exist
CONTEXT:  SQL statement "ALTER TABLE mytable_bak_old RENAME TO mytable_bak"
PL/pgSQL function foo(regclass) line 6 at EXECUTE statement

就好像第三次执行被缓存,保存了表名称。

有趣的是:如果我删除最后一行并执行它,它会按预期工作,但我仍然需要最后一行(上面的代码)来执行:

CREATE OR REPLACE FUNCTION foo(_t regclass)
  RETURNS void AS
$func$
BEGIN
   EXECUTE 'ALTER TABLE '|| _t ||'_bak RENAME TO '|| _t || '_old';
   EXECUTE 'TRUNCATE TABLE '|| _t || '_old';
   EXECUTE 'ALTER TABLE '|| _t ||' RENAME TO '|| _t || '_bak';
END
$func$ LANGUAGE plpgsql;

我在这里缺少什么?尤其是最后这句话?

最佳答案

对象标识符数据类型regclass内部是系统目录表pg_class的oid。您作为参数传递的字符串 'mytable' 在“方便转换”中立即解析为对象标识符到 regclass。如果您稍后重命名该表,_t 将在下一次调用中解析为新名称。

  • _t 在第三次 EXECUTE 中重命名为 mytable_bak
  • 错误发生在您的第四个EXECUTE中,其中_t被解析为mytable_bak(正确!),并且您最终尝试重命名表mytable_bak_old - 正如您在错误消息中看到的。

在开始命名谜题之前提取表名称一次:

CREATE OR REPLACE FUNCTION foo(_t regclass)
  RETURNS void AS
$func$
DECLARE
  _tbl text := _t::text;  -- "early binding"
BEGIN
   EXECUTE format('ALTER TABLE %I_bak RENAME TO %1$s_old', _tbl);
   EXECUTE 'TRUNCATE TABLE ' || _tbl || '_old';
   EXECUTE format('ALTER TABLE %1$s RENAME TO %1$s_bak', _tbl);
   EXECUTE format('ALTER TABLE %1$s_old RENAME TO %1$s', _tbl);
END
$func$ LANGUAGE plpgsql;

在 Postgres 9.4 中进行了测试并适用于我。

请注意,这仅适用于不需要双引号且在 search_path 中可见的合法小写表名。否则您会收到一条错误消息 - 您需要执行更多操作才能正确连接名称。但 SQL 注入(inject)是不可能的。

或者只是传递一个 text 字符串并在其中使用 quote_ident() 对其进行转义:

关于postgresql - 使用 regclass 的动态 SQL 的意外行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31041220/

相关文章:

MySql 在存储过程中的 IF THEN END IF 中使用 OR

创建过程时mysql语法错误1064

ruby - 如何在Rails中实现投票而不投票gem

python - 在 Win10 上从 Python 备份 Postgres

sql - 如何选择列值为空的行?

php - 在 mysql/PHP 中执行存储过程时出现错误 "Commands out of sync, you can' t 立即运行命令

postgresql - plpgsql - pgAdmin 4 不显示 RAISE 消息(例如,通知)

php - PostgreSQL 数据库中异常多的独占锁

postgresql - 如何在 pl/pgsql 函数中将 int 转换为时间

database - 外部表引用Postgres 9.5