DO 脚本中的 PSQL 命令行参数

PSQL Command Line Arguments in DO script

我有一个脚本 NewSchemaSafe.sql 可以根据项目目录创建一个新模式;它是从 Windows 命令行调用的,如下所示:

for %%a in (.) do set this=%%~na
-- other stuff here
psql -U postgres -d SLSM -e -v v1=%this% -f "NewSchemaSafe.sql"

NewSchemaSafe.sql如下:

-- NewSchemaSafe.sql
-- NEW SCHEMA SETUP 
--    - checks if schema exists
--    - if yes, renames existing with current monthyear as suffix
-- NOTE: will always delete any schema with the 'rename' name (save_schema)
--       since any schema thus named must have resulted from this script 
--       on this date - so, y'know, no loss.
SET search_path TO :v1, public; -- kludge coz can't pass :v1 to DO
DO
$$
DECLARE
       this_schema TEXT:= current_schema()::TEXT;
       this_date TEXT:= replace(current_date::TEXT,'-','');
       save_schema TEXT:= this_schema||this_date;
BEGIN
    IF this_schema <> 'public'
    THEN
        RAISE NOTICE 'Working in schema %', this_schema;
        IF EXISTS(
            SELECT schema_name
              FROM information_schema.schemata
              WHERE schema_name = save_schema)
        THEN
           EXECUTE 'DROP SCHEMA '||save_schema||' CASCADE;';
        END IF;
        IF NOT EXISTS(
            SELECT schema_name
              FROM information_schema.schemata
              WHERE schema_name = this_schema
          )
        THEN
          EXECUTE 'CREATE SCHEMA '||this_schema||';';
        ELSE
          EXECUTE 'ALTER SCHEMA '||this_schema|| ' RENAME TO '|| save_schema ||';';
          EXECUTE 'COMMENT ON SCHEMA '|| save_schema ||' IS ''schema renamed by SLSM creation on '|| this_date ||'''';
          EXECUTE 'CREATE SCHEMA '||this_schema||';';
        END IF;
    ELSE
        RAISE NOTICE 'SCHEMA IS % SO PARAMETER WAS NOT PASSED OR DID NOT STICK', this_schema;
    END IF;
END
$$;

现在我知道 SET 发生了,因为我可以在命令行输出中看到它。然而,脚本的其余部分死了(优雅地,如预期的那样),因为它似乎认为 current_schemapublic:脚本产生

psql: NewSchemaSafe.sql:39: NOTICE:  SCHEMA IS public SO PARAMETER WAS NOT PASSED OR DID NOT STICK

我最初尝试将 :v1 传递给 DO 循环的 DECLARE 块,如下所示:

 DECLARE
       this_schema text := :v1 ;
       this_date text := replace(current_date::text,'-','');
       save_schema text := this_schema||this_date;
  [snip]

但这只是半途而废:它会引发语法错误 -

psql:NewSchemaSafe.sql:40: ERROR:  syntax error at or near ":"
LINE 4:        this_schema text := :v1 ;

批处理文件中 %this% 是否包含在引号中没有区别。

像往常一样,两个问题:

  1. 为什么 set search path 声明没有 'stick',而我可以 看到它正在执行?更新:不相关,请忽略。
  2. 如何将 :v1 参数传递给 DO 脚本本身?

环境:PostgreSQL 9.3.5 64 位 (Win);

奇怪之处:我确定这个脚本在两天前就可以运行,唯一的变化是删除了 geany 插入的字节顺序标记(UTF BOM 造成 psql 堵嘴)。

UPDATE:前几天它起作用的原因是它在 运行 正在考虑的模式 did 存在。如果作为 :v1 传递的模式名称不存在 - 这使得 :v1 传递给 DO 变得更加重要,因此可以更直接地使用它。

因为PL块实际上是代码中的文本常量,所以内部变量不会以通常的方式在其中被替换。幸运的是,可以使用会话变量在不同 SQL/PL 块之间共享数据:

set foo.bar to :v1; -- Name should contains the dot, don't ask me why 
show foo.bar; -- Check that the value was assigned 
do $$
declare
  myvar text := current_setting('foo.bar');
begin
  raise info '%', myvar; -- Output variable value
end $$;

确保变量已赋值,如果未赋值则设置默认值:

\if :{?v1}
  set foo.bar to :v1;
\else
  set foo.bar to 'default';
\endif

更多详情:
https://www.postgresql.org/docs/current/app-psql.html#PSQL-METACOMMAND-IF https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-INTERPOLATION

创建临时 函数 而不是使用 DO 语句。如果需要传参就是解决方法

CREATE FUNCTION pg_temp.f_create_schema(_schema text)  -- note function schema "pg_temp"
  RETURNS void AS 
$func$
DECLARE
   _date        text := to_char(current_date, 'YYYYMMDD');
   _save_schema text := _schema || _date;  -- unescaped identifier
BEGIN
   IF EXISTS (SELECT 1 FROM information_schema.schemata
              WHERE  schema_name = _save_schema) THEN  -- unescaped identifier
      EXECUTE format('DROP SCHEMA %I CASCADE', _save_schema);  -- escaped identifier!
   END IF;

   IF EXISTS (SELECT 1 FROM information_schema.schemata
              WHERE  schema_name = _schema) THEN
      EXECUTE format(
        'ALTER SCHEMA %1$I RENAME TO %2$I;
         COMMENT ON SCHEMA %2$I IS $c$Schema renamed by SLSM creation on %3$s.$c$'
       , _schema, _save_schema, _date);
   END IF;

   EXECUTE 'CREATE SCHEMA ' || quote_ident(_schema);
END
$func$ LANGUAGE plpgsql;

致电:

SELECT pg_temp.f_create_schema('Foo');  -- function name must be schema-qualified

来自 psql SQL interpolation 使用变量 v1:

SELECT pg_temp.f_create_schema(:'v1');

您为 _schema 传递的架构名称 区分大小写 未加引号

pg_temp 是一个伪名称,可在内部自动转换为当前会话的临时模式。临时架构中的所有对象都在 会话结束时消失

"Temporary" 手册中没有明确记录功能,但可以安全使用。

  • Is there such thing as a "temp function"?

如果您需要在同一个会话中针对不同的数据库执行一次(或几次)函数,这很有意义。为了在同一数据库中重复使用,请改为创建一个普通函数。

当然,您需要 TEMPORARY privilege 数据库 - 用户默认拥有。

在此过程中,我改进了几件事:

  • 转义标识符以防止SQL注入和普通语法错误。对于更复杂的情况,请使用 quote_ident() or format()

  • 您不需要将分号连接到单个 SQL 命令的末尾。

  • 您可以一次 EXECUTE 多个 SQL 语句。 (现在你需要一个分号 between 语句。)

  • 使用nested dollar quotes to avoid quoting hell.

也有各种解决方法

顺便说一句,由于历史原因,customized options ("session variables") 需要一个由两部分组成的名称(形式为 extension.variable)。事实证明,它有助于尽可能避免命名冲突。