Subject: not fully correct error message

From: Pavel Stehule <[email-masked]>
Date: Thu, 1 Jan 2026 08:10:13 +0100

Hi

I tested one use case, and maybe I found little bit possible error message

create procedure test()
as $$
begin
  vacuum;
end;
$$ language plpgsql;

(2026-01-01 08:04:05) postgres=# call test();
ERROR:  25001: VACUUM cannot be executed from a function
CONTEXT:  SQL statement "vacuum"
PL/pgSQL function test() line 3 at SQL statement
LOCATION:  PreventInTransactionBlock, xact.c:3695
(2026-01-01 08:09:18) postgres=#

should be "VACUUM cannot be executed from a function or a procedure"
instead ?

Regards

Pavel

---

From: jian he <[email-masked]>
Date: Thu, 1 Jan 2026 18:05:12 +0800

On Thu, Jan 1, 2026 at 3:10 PM Pavel Stehule <[email-masked]> wrote:
> should be "VACUUM cannot be executed from a function or a procedure" instead ?
>

hi.
"VACUUM cannot be executed from a function or a procedure"
looks good to me.

similarly, in ExecWaitStmt we have:
        ereport(ERROR,
                errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                errmsg("WAIT FOR must be only called without an active
or registered snapshot"),
                errdetail("WAIT FOR cannot be executed from a function
or a procedure or within a transaction with an isolation level higher
than READ COMMITTED."));

PreventInTransactionBlock is used in so many places, but this error message:
``
(errmsg("%s cannot be executed from a function", stmtType)));
``
only appears once in the regress tests.
maybe we can add some dummy tests for it.

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 3 Jan 2026 07:34:52 +0100

Hi

> maybe we can add some dummy tests for it.

here is a patch (with small regress test)

Regards

Pavel
From aa57e3a80e5ee7bce121addc607d198e67941fc7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 3 Jan 2026 07:32:26 +0100
Subject: [PATCH] VACUUM cannot be executed inside a function or a procedure

---
 src/backend/access/transam/xact.c                  |  2 +-
 .../plpgsql/src/expected/plpgsql_transaction.out   | 13 +++++++++++++
 src/pl/plpgsql/src/sql/plpgsql_transaction.sql     | 14 ++++++++++++++
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 842faa44232..5cf85e2f35c 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -3695,7 +3695,7 @@ PreventInTransactionBlock(bool isTopLevel, const char *stmtType)
 		ereport(ERROR,
 				(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
 		/* translator: %s represents an SQL statement name */
-				 errmsg("%s cannot be executed from a function", stmtType)));
+				 errmsg("%s cannot be executed from a function or a procedure", stmtType)));
 
 	/* If we got past IsTransactionBlock test, should be in default state */
 	if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
index adff10fa6d6..bcdf7958823 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
@@ -736,6 +736,19 @@ SELECT * FROM test1;
  2 | 
 (2 rows)
 
+-- VACUUM cannot be executed inside a function or a procedure
+CREATE PROCEDURE vacuum_inside_procedure()
+LANGUAGE plpgsql AS $$
+BEGIN
+  VACUUM;
+END;
+$$;
+-- error
+CALL vacuum_inside_procedure();
+ERROR:  VACUUM cannot be executed from a function or a procedure
+CONTEXT:  SQL statement "VACUUM"
+PL/pgSQL function vacuum_inside_procedure() line 3 at SQL statement
+DROP PROCEDURE vacuum_inside_procedure();
 DROP TABLE test1;
 DROP TABLE test2;
 DROP TABLE test3;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
index c73fca7e03e..5e8da3ec4c4 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
@@ -631,6 +631,20 @@ $$;
 SELECT * FROM test1;

+-- VACUUM cannot be executed inside a function or a procedure
+
+CREATE PROCEDURE vacuum_inside_procedure()
+LANGUAGE plpgsql AS $$
+BEGIN
+  VACUUM;
+END;
+$$;
+
+-- error
+CALL vacuum_inside_procedure();
+
+DROP PROCEDURE vacuum_inside_procedure();
+
 DROP TABLE test1;
 DROP TABLE test2;
 DROP TABLE test3;
-- 
2.52.0

---

From: Andreas Karlsson <[email-masked]>
Date: Sat, 3 Jan 2026 12:35:37 +0100

> here is a patch (with small regress test)

Looks like a good change to me since the obvious question many people 
would ask themselves after seeing this error is if running it in a 
procedure would work instead and it does not. And this error message 
changes saves those people time which is a great thing.

Tried out this patch and it worked and, as expected, also improved the 
error for other commands like CREATE DATABASE and REINDEX CONCURRENTLY 
so when this is commit the commit message should make it clear this is 
not just about VACUUM.

A small suggestion is to change the message from:

"%s cannot be executed from a function or a procedure"

to:

"%s cannot be executed from a function or procedure"

I am not a native speaker but I think the second flows better.

Andreas

---

From: Marcos Pegoraro <[email-masked]>
Date: Sat, 3 Jan 2026 09:22:30 -0300

> here is a patch (with small regress test)

An anonymous block doesn't accept vacuum too.
Wouldn't it be better to specify what kind of block you are running instead
of
function, procedure or anonymous block ?

regards
Marcos

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 3 Jan 2026 13:34:46 +0100

so 3. 1. 2026 v 13:23 odesílatel Marcos Pegoraro <[email-masked]> napsal:

> Em sáb., 3 de jan. de 2026 às 03:35, Pavel Stehule <
> [email-masked]> escreveu:
>
> Wouldn't it be better to specify what kind of block you are running
> instead of

It is correct, but maybe too long.

Generally, there is a term "routine" as a synonym for "function, procedure
or any stored procedure code", but I am not sure how much is accepted by
the community

---

From: Andreas Karlsson <[email-masked]>
Date: Sat, 3 Jan 2026 13:39:28 +0100

On 1/3/26 1:22 PM, Marcos Pegoraro wrote:
> Em sáb., 3 de jan. de 2026 às 03:35, Pavel Stehule 
> <[email-masked] <mailto:[email-masked]>> escreveu:
> 

Maybe out of some kind of correctness, but it seems less useful to me 
since the obvious question an end user would ask after trying to run 
VACUUM in a function is if they can do so in a procedure instead so we 
may as well tell them right away.

A potential third option would be to take your solution but to add a 
HINT about that it needs to run as a top-level statement outside any 
transactions, but I kinda liked how simple the original patch was.

Andreas

---

From: Tom Lane <[email-masked]>
Date: Sat, 03 Jan 2026 13:03:42 -0500

>> instead of function, procedure or anonymous block ?

I don't think PreventInTransactionBlock has ready access to that
information.

> transactions, but I kinda liked how simple the original patch was.

Yeah, I like just adding "or procedure" and calling it good.
I do not think we need a regression test, either ...

Poking around, I also found this:

src/backend/commands/wait.c:                              errdetail("WAIT FOR cannot be executed from a function or a procedure or within a transaction with an isolation level higher than READ COMMITTED."));

which is also not great grammar.  What do you think of "WAIT FOR
cannot be executed from a function or procedure, nor within a
transaction with an isolation level higher than READ COMMITTED." ?

			regards, tom lane

---

From: Andreas Karlsson <[email-masked]>
Date: Sat, 3 Jan 2026 19:44:44 +0100

> I do not think we need a regression test, either ...

Yeah, let's keep it simple.

> transaction with an isolation level higher than READ COMMITTED." ?

Much better!

Andreas