Asking this question for a colleague who ran into this behaviour with PostgreSQL. Hoping somebody out there can solve this puzzle for us.
The following logs Rows updated: 3
when run:
drop table if exists test;
create table test (str text);
insert into test(str)
values ('row1'), ('row2'), ('row3');
do $$
declare
_row_count bigint = -1;
begin
prepare update_test as
update test
set str = str
where true
returning 1; -- why is this needed?
execute 'execute update_test';
get diagnostics _row_count := row_count;
deallocate update_test;
raise notice 'Rows updated: %', _row_count;
end
$$ language plpgsql;
However, the returning 1
line should not be necessary, as we’re looking at the rows count diagnostic. Indeed, running the contents of the anonymous code block directly logs 3 even with the returning 1
removed.
But running the whole thing with the returning 1
removed logs a value of zero, not the expected 3.
Update:
The example above is a simplication of the problem. What we’re actually trying to achieve here is a general purpose procedure that can run a variety of named prepared statements. As such, the dynamic call is necessary.
Here is a bigger example of what we’re trying to achieve:
-- general purpose procedure
drop procedure if exists run_sql(text);
create procedure run_sql(named_statement text)
as $$
declare
_row_count bigint = -1;
begin
-- do some stuff
execute 'execute ' || named_statement;
get diagnostics _row_count := row_count;
raise notice 'Rows updated: %', _row_count;
-- do some other stuff
end
$$ language plpgsql;
-- example usage
prepare update_test as
update test
set str = str
where true
returning 1; -- why is this needed?
call run_sql('update_test');
deallocate update_test;
So we cannot simplify the code in the procedure to call execute directly: it has to be a dynamic call. But it would be good if the passed in statements did not have to have returning 1
on the end.
2
Answers
To follow up on my comment.
execute 'execute ...
is redundant so simplify to:The construction is rather strange as well: The plpgsql statement
EXECUTE
doesn’t cache the query plan. And because of this you usePREPARE
to create a query plan that can be repeated. Caching the query plan is the only thing that could have some value in here, anything to avoid SQL injection can be done by placeholders in plpgsql orformat()
in combination with plpgsql’sEXECUTE
.And if you would use a plain
UPDATE
in your code, the plan would have been cached by default. Using less code and simpler code would result in the same functionality: Secure and fast code.Rows updated: 3