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

时间:2015-06-25 04:28:59

标签: 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;

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

1 个答案:

答案 0 :(得分:4)

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

  • _t已在第3个mytable_bak重命名为EXECUTE
  • 错误发生在你的第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注入是不可能的。

或者只是传递一个text字符串并使用quote_ident()将其转义: