【问题标题】:PSQL Command Line Arguments in DO scriptDO 脚本中的 PSQL 命令行参数
【发布时间】:2016-11-05 13:59:48
【问题描述】:

我有一个脚本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 语句为什么不“坚持”,当我可以的时候 看到它执行了吗?更新:不相关,请忽略。
  2. 如何将:v1 参数传递给DO 脚本本身?

环境:PostgreSQL 9.3.5 64位(Win);

奇怪之处:我确信这个脚本在两天前可以工作,唯一的变化是删除了 geany 插入的字节顺序标记(UTF BOM 使 psql gag)。

UPDATE:前几天它工作的原因是它在考虑中的架构确实存在的情况下运行。如果模式名称以:v1 的形式传递不存在,则更改search_path(尝试从current_schema 中找到所需的模式)将无济于事 - 这使得@ 更重要987654342@ 被传递给DO,因此可以更直接地使用。

【问题讨论】:

  • 回答“如何将 :v1 参数传递给 DO 脚本本身?”这是诀窍:在DO 之外执行prepare foo as select :'v1';,然后在DO 块内... execute 'execute foo' into this_schema;。要删除准备好的语句,请执行 deallocate foo;
  • 另一种方式:会话变量。在DO 之外:set foo.bar to :v1;(名称中的点是必需的)和在DO 块内this_schema := current_setting('foo.bar');
  • @Abelisto 能否请您将此作为答案,以便将其标记为答案?我没有让prepare 版本工作,但没有花太多时间:另外,set 版本更简洁,如果变量类型声明是,可以包含在DECLARE 块中添加,即this_schema TEXT := current_setting('foo.bar');。谢谢,GT。

标签: postgresql command-line parameter-passing plpgsql psql


【解决方案1】:

因为 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

【讨论】:

  • 当未传入“v1”值时,脚本会报错“在“:”或附近出现语法错误:第 1 行:SET att.cacheHostsInput 到:cacheHostsInput;”如果没有传入属性,即可选参数,我们如何确保脚本以某个默认值运行?
  • @sanchin 答案已更新
【解决方案2】:

创建一个临时函数,而不是使用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

从带有SQL interpolation 的psql 使用变量v1

SELECT pg_temp.f_create_schema(:'v1');

您为_schema 传递的架构名称​​区分大小写并且不加引号

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

“临时”功能没有在手册中明确记录,但可以安全使用。

如果您需要在同一个会话中为不同的数据库调用一次(或几次)该函数,这是有道理的。为了在同一个数据库中重复使用,请创建一个普通函数。

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

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

还有各种解决方法

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

【讨论】:

  • 我没有意识到将命令行参数传递给functionDO 容易得多:因为模式创建的事情发生了几十次一个月应该是一个函数。美元报价也是我真正需要掌握的东西。
  • @GT.:请注意,以前的版本有一个只显示非标准名称的偷偷摸摸的错误:对系统目录或信息架构的查询必须使用 未转义字符串。也简化了一些。
猜你喜欢
  • 2020-04-15
  • 1970-01-01
  • 1970-01-01
  • 2013-03-03
  • 1970-01-01
  • 1970-01-01
  • 2012-01-24
  • 1970-01-01
  • 2015-02-15
相关资源
最近更新 更多