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_schema
是 public
:脚本产生
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%
是否包含在引号中没有区别。
像往常一样,两个问题:
- 为什么
set search path
声明没有 'stick',而我可以
看到它正在执行?更新:不相关,请忽略。
- 如何将
: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
)。事实证明,它有助于尽可能避免命名冲突。
我有一个脚本 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_schema
是 public
:脚本产生
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%
是否包含在引号中没有区别。
像往常一样,两个问题:
- 为什么
set search path
声明没有 'stick',而我可以 看到它正在执行?更新:不相关,请忽略。 - 如何将
: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()
orformat()
。您不需要将分号连接到单个 SQL 命令的末尾。
您可以一次
EXECUTE
多个 SQL 语句。 (现在你需要一个分号 between 语句。)使用nested dollar quotes to avoid quoting hell.
也有各种解决方法:
顺便说一句,由于历史原因,customized options ("session variables") 需要一个由两部分组成的名称(形式为 extension.variable
)。事实证明,它有助于尽可能避免命名冲突。