为什么 postgres set_config 的 is_local = true 不为整个事务保留变量?
why does postgres set_config's is_local = true not persist the variable for the whole transaction?
我有一个函数,它使用 set_config
和 is_local = true
来设置变量。
现在我希望在同一事务中使用带有 current_settings
的变量的 select 语句能够访问该变量,因为文档状态:
set_config(setting_name, new_value, is_local) ... set parameter and
return new value
set_config sets the parameter setting_name to new_value. If is_local
is true, the new value will only apply to the current transaction.
但这对我来说不起作用,我得到
ERROR: unrecognized configuration parameter "auth.tenant_id"
SQL state: 42704
有什么我错的地方吗?
这是函数:
CREATE OR REPLACE FUNCTION auth.authorize(IN a_user_id uuid)
RETURNS boolean
LANGUAGE 'plpgsql'
SECURITY DEFINER
AS $BODY$
declare
v_tenant_id uuid;
v_user_role auth.user_role;
begin
select tenant_id, user_role into strict v_tenant_id, v_user_role from auth.authorizations where user_id = a_user_id;
perform set_config('auth.tenant_id', v_tenant_id::text, true);
perform set_config('auth.user_role', v_user_role::text, true);
return true;
exception when no_data_found then return false;
end;
$BODY$;
这是第二个 select 语句失败的事务
begin;
select * from auth.authorize(uuid('180e1b14-21e5-4e66-a9b8-db09139d6278'));
select current_setting('auth.tenant_id') as tenant_id, current_setting('auth.user_role') as user_role;
commit;
set_config(is_local => true)
的效果似乎被限制在您的 BEGIN … EXCEPTION
区块创建的隐式子交易中。
(跳到我的 TL;DR 解决方案的结尾。)
下面是我执行类似操作时发生的情况,但在 BEGIN … EXCEPTION
子事务之外调用 set_config()
:
create or replace function set_config_outside_of_begin_except_block(denominator int)
returns bool
language 'plpgsql'
as $body$
begin
perform set_config('my.setting', 'set before subtransaction', true);
begin
perform 10 / denominator;
return true;
exception when division_by_zero then
return false;
end;
end;
$body$;
begin
select current_setting('my.setting') as my_setting;
select set_config_outside_of_begin_except_block(1);
rollback;
CREATE FUNCTION
BEGIN
ERROR: unrecognized configuration parameter "my.setting"
set_config_outside_of_begin_except_block
------------------------------------------
t
(1 row)
my_setting
---------------------------
set before subtransaction
(1 row)
ROLLBACK
将 set_config()
调用移至 BEGIN … EXCEPTION
块之前,my.setting
的新设置已在我们的函数调用之外保留。
而且,正如您接下来将看到的,如果在上面的 BEGIN … EXCEPTION
块中发生异常甚至无关紧要,这是有道理的,因为 set_config()
被称为 在 输入 block/subtransaction 之前:
begin
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
set_config_outside_of_begin_except_block
------------------------------------------
f
(1 row)
my_setting
---------------------------
set before subtransaction
(1 row)
ROLLBACK
现在,我将稍微修改代码以更接近您的示例,并更接近于重现您描述的行为:
create or replace function set_config_in_begin_except_block(denominator int)
returns bool
language 'plpgsql'
as $body$
begin
perform set_config('my.setting', 'set in subtransaction', true);
perform 10 / denominator;
return true;
exception when division_by_zero then
return false;
end;
$body$;
begin;
select set_config_in_begin_except_block(0);
select current_setting('my.setting') as my_setting;
rollback;
CREATE FUNCTION
BEGIN
set_config_in_begin_except_block
----------------------------------
f
(1 row)
my_setting
------------
(1 row)
ROLLBACK
begin
select set_config_in_begin_except_block(1);
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
set_config_in_begin_except_block
----------------------------------
t
(1 row)
my_setting
-----------------------
set in subtransaction
(1 row)
ROLLBACK
请注意,现在,
BEGIN … EXCEPTION
块确实导致对 my.setting
的更改丢失,
- 但只有当异常 (
division_by_zero
) 确实被引发时,
- 即使
division_by_zero
是由语句 after set_config()
. 触发的
所以,它有点接近您描述的行为,但又不完全是。您的设置不会在 BEGIN … EXCEPTION
块之外持续存在 无论 是否遇到 no_data_found
异常。
绕个弯路:我偶然发现了你的问题,因为我想知道我是否可以将设置视为与(子)事务堆栈一起展开的堆栈。事实证明我可以:
create or replace function set_config_stacked(denominator int)
returns bool
language 'plpgsql'
as $body$
begin
perform set_config('my.setting', 'set before substraction', true);
begin
perform set_config('my.setting', 'set within subtransaction before division', true);
perform 10 / denominator;
return true;
exception when division_by_zero then
return false;
end;
end;
$body$;
begin;
select set_config_stacked(1);
select current_setting('my.setting') as my_setting;
rollback;
CREATE FUNCTION
BEGIN
set_config_stacked
--------------------
t
(1 row)
my_setting
-------------------------------------------
set within subtransaction before division
(1 row)
ROLLBACK
begin;
select set_config_stacked(0);
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
set_config_stacked
--------------------
f
(1 row)
my_setting
-------------------------
set before substraction
(1 row)
ROLLBACK
所以当 BEGIN … EXCEPTION
块的子交易关闭时 my.setting
实际上恢复到它以前的设置。
我需要最后一项更改来重现您的函数的行为:
create table tab (id uuid primary key, stuff text);
insert into tab (id, stuff) values (uuid('180e1b14-21e5-4e66-a9b8-db09139d6278'), 'some stuff');
create or replace function set_config_stacked(id$ uuid)
returns bool
language 'plpgsql'
as $body$
declare
rec tab%rowtype;
begin
perform set_config('my.setting', 'set before substraction', true);
begin
perform set_config('my.setting', 'set within subtransaction before SELECT', true);
select * into strict rec from tab where id = id$;
perform set_config('my.setting', 'set within subtransaction after SELECT', true);
return true;
exception when no_data_found then
return false;
end;
end;
$body$;
begin;
select set_config_stacked(uuid('180e1b14-21e5-4e66-a9b8-db09139d6278')); -- exist
select current_setting('my.setting') as my_setting;
rollback;
CREATE TABLE
INSERT 1 0
CREATE FUNCTION
set_config_stacked
--------------------
t
(1 row)
my_setting
----------------------------------------
set within subtransaction after SELECT
(1 row)
ROLLBACK
begin;
select set_config_stacked(uuid('cc7ad0c3-7e3a-49a0-b7d8-7b4093ae0028')); -- doesn't exist
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
set_config_stacked
--------------------
f
(1 row)
my_setting
-------------------------
set before substraction
(1 row)
ROLLBACK
如您所见,您所描述的行为仍有一方面我无法重现。此版本的 set_config_stacked()
围绕 no_data_found
异常运行,其行为与基于 division_by_zero
异常的先前版本相同。
但是,您的示例表明当遇到 no 异常时,您的设置也不会在 auth.authorize()
之外持续存在。这让我感到困惑,而且我无法重现,至少在 Postgres 14 上是这样。
尽管如此,您的问题可以通过将调用移至 BEGIN … EXCEPTION … END
块下方的 set_config()
来解决。您仍然希望在该块 内 设置变量,但是执行这些调用和 return true
来自要创建的新外部 BEGIN … END
块(没有 EXCEPTION
).
我有一个函数,它使用 set_config
和 is_local = true
来设置变量。
现在我希望在同一事务中使用带有 current_settings
的变量的 select 语句能够访问该变量,因为文档状态:
set_config(setting_name, new_value, is_local) ... set parameter and return new value
set_config sets the parameter setting_name to new_value. If is_local is true, the new value will only apply to the current transaction.
但这对我来说不起作用,我得到
ERROR: unrecognized configuration parameter "auth.tenant_id"
SQL state: 42704
有什么我错的地方吗?
这是函数:
CREATE OR REPLACE FUNCTION auth.authorize(IN a_user_id uuid)
RETURNS boolean
LANGUAGE 'plpgsql'
SECURITY DEFINER
AS $BODY$
declare
v_tenant_id uuid;
v_user_role auth.user_role;
begin
select tenant_id, user_role into strict v_tenant_id, v_user_role from auth.authorizations where user_id = a_user_id;
perform set_config('auth.tenant_id', v_tenant_id::text, true);
perform set_config('auth.user_role', v_user_role::text, true);
return true;
exception when no_data_found then return false;
end;
$BODY$;
这是第二个 select 语句失败的事务
begin;
select * from auth.authorize(uuid('180e1b14-21e5-4e66-a9b8-db09139d6278'));
select current_setting('auth.tenant_id') as tenant_id, current_setting('auth.user_role') as user_role;
commit;
set_config(is_local => true)
的效果似乎被限制在您的 BEGIN … EXCEPTION
区块创建的隐式子交易中。
(跳到我的 TL;DR 解决方案的结尾。)
下面是我执行类似操作时发生的情况,但在 BEGIN … EXCEPTION
子事务之外调用 set_config()
:
create or replace function set_config_outside_of_begin_except_block(denominator int)
returns bool
language 'plpgsql'
as $body$
begin
perform set_config('my.setting', 'set before subtransaction', true);
begin
perform 10 / denominator;
return true;
exception when division_by_zero then
return false;
end;
end;
$body$;
begin
select current_setting('my.setting') as my_setting;
select set_config_outside_of_begin_except_block(1);
rollback;
CREATE FUNCTION
BEGIN
ERROR: unrecognized configuration parameter "my.setting"
set_config_outside_of_begin_except_block
------------------------------------------
t
(1 row)
my_setting
---------------------------
set before subtransaction
(1 row)
ROLLBACK
将 set_config()
调用移至 BEGIN … EXCEPTION
块之前,my.setting
的新设置已在我们的函数调用之外保留。
而且,正如您接下来将看到的,如果在上面的 BEGIN … EXCEPTION
块中发生异常甚至无关紧要,这是有道理的,因为 set_config()
被称为 在 输入 block/subtransaction 之前:
begin
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
set_config_outside_of_begin_except_block
------------------------------------------
f
(1 row)
my_setting
---------------------------
set before subtransaction
(1 row)
ROLLBACK
现在,我将稍微修改代码以更接近您的示例,并更接近于重现您描述的行为:
create or replace function set_config_in_begin_except_block(denominator int)
returns bool
language 'plpgsql'
as $body$
begin
perform set_config('my.setting', 'set in subtransaction', true);
perform 10 / denominator;
return true;
exception when division_by_zero then
return false;
end;
$body$;
begin;
select set_config_in_begin_except_block(0);
select current_setting('my.setting') as my_setting;
rollback;
CREATE FUNCTION
BEGIN
set_config_in_begin_except_block
----------------------------------
f
(1 row)
my_setting
------------
(1 row)
ROLLBACK
begin
select set_config_in_begin_except_block(1);
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
set_config_in_begin_except_block
----------------------------------
t
(1 row)
my_setting
-----------------------
set in subtransaction
(1 row)
ROLLBACK
请注意,现在,
BEGIN … EXCEPTION
块确实导致对my.setting
的更改丢失,- 但只有当异常 (
division_by_zero
) 确实被引发时, - 即使
division_by_zero
是由语句 afterset_config()
. 触发的
所以,它有点接近您描述的行为,但又不完全是。您的设置不会在 BEGIN … EXCEPTION
块之外持续存在 无论 是否遇到 no_data_found
异常。
绕个弯路:我偶然发现了你的问题,因为我想知道我是否可以将设置视为与(子)事务堆栈一起展开的堆栈。事实证明我可以:
create or replace function set_config_stacked(denominator int)
returns bool
language 'plpgsql'
as $body$
begin
perform set_config('my.setting', 'set before substraction', true);
begin
perform set_config('my.setting', 'set within subtransaction before division', true);
perform 10 / denominator;
return true;
exception when division_by_zero then
return false;
end;
end;
$body$;
begin;
select set_config_stacked(1);
select current_setting('my.setting') as my_setting;
rollback;
CREATE FUNCTION
BEGIN
set_config_stacked
--------------------
t
(1 row)
my_setting
-------------------------------------------
set within subtransaction before division
(1 row)
ROLLBACK
begin;
select set_config_stacked(0);
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
set_config_stacked
--------------------
f
(1 row)
my_setting
-------------------------
set before substraction
(1 row)
ROLLBACK
所以当 BEGIN … EXCEPTION
块的子交易关闭时 my.setting
实际上恢复到它以前的设置。
我需要最后一项更改来重现您的函数的行为:
create table tab (id uuid primary key, stuff text);
insert into tab (id, stuff) values (uuid('180e1b14-21e5-4e66-a9b8-db09139d6278'), 'some stuff');
create or replace function set_config_stacked(id$ uuid)
returns bool
language 'plpgsql'
as $body$
declare
rec tab%rowtype;
begin
perform set_config('my.setting', 'set before substraction', true);
begin
perform set_config('my.setting', 'set within subtransaction before SELECT', true);
select * into strict rec from tab where id = id$;
perform set_config('my.setting', 'set within subtransaction after SELECT', true);
return true;
exception when no_data_found then
return false;
end;
end;
$body$;
begin;
select set_config_stacked(uuid('180e1b14-21e5-4e66-a9b8-db09139d6278')); -- exist
select current_setting('my.setting') as my_setting;
rollback;
CREATE TABLE
INSERT 1 0
CREATE FUNCTION
set_config_stacked
--------------------
t
(1 row)
my_setting
----------------------------------------
set within subtransaction after SELECT
(1 row)
ROLLBACK
begin;
select set_config_stacked(uuid('cc7ad0c3-7e3a-49a0-b7d8-7b4093ae0028')); -- doesn't exist
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
set_config_stacked
--------------------
f
(1 row)
my_setting
-------------------------
set before substraction
(1 row)
ROLLBACK
如您所见,您所描述的行为仍有一方面我无法重现。此版本的 set_config_stacked()
围绕 no_data_found
异常运行,其行为与基于 division_by_zero
异常的先前版本相同。
但是,您的示例表明当遇到 no 异常时,您的设置也不会在 auth.authorize()
之外持续存在。这让我感到困惑,而且我无法重现,至少在 Postgres 14 上是这样。
尽管如此,您的问题可以通过将调用移至 BEGIN … EXCEPTION … END
块下方的 set_config()
来解决。您仍然希望在该块 内 设置变量,但是执行这些调用和 return true
来自要创建的新外部 BEGIN … END
块(没有 EXCEPTION
).