From 60977d2b3fc33071bea02879c92b95e4466ab6d8 Mon Sep 17 00:00:00 2001
From: Joel Jacobson <joel@compiler.org>
Date: Fri, 29 May 2026 06:15:34 +0200
Subject: [PATCH v7 1/3] Serialize routine definition changes with dependency
 recording

Dependency recording now locks referenced objects before inserting
pg_depend rows.  Routine definition changes also need to participate in
that object-lock protocol, because stored expressions can depend on
function properties such as volatility, strictness, or SQL-body
contents.

Take AccessExclusiveLock on the pg_proc object before CREATE OR REPLACE
FUNCTION updates an existing routine, and before ALTER FUNCTION changes
routine properties.  After waiting, refetch the pg_proc tuple and repeat
the ownership and object-kind checks, because the routine could have
been dropped or reassigned while we waited.

Authors: Andreas Karlsson, Joel Jacobson, Arne Roland
Discussion: https://www.postgresql.org/message-id/TY4PR01MB17718A4DE63020A9EA5E9CB6594382%40TY4PR01MB17718.jpnprd01.prod.outlook.com
---
 src/backend/catalog/pg_proc.c       | 22 ++++++++++++++++++++++
 src/backend/commands/functioncmds.c | 29 +++++++++++++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 5df4b3f7a91..d5cc7589091 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -35,6 +35,7 @@
 #include "parser/parse_coerce.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
+#include "storage/lmgr.h"
 #include "tcop/pquery.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
@@ -394,6 +395,7 @@ ProcedureCreate(const char *procedureName,
 	{
 		/* There is one; okay to replace it? */
 		Form_pg_proc oldproc = (Form_pg_proc) GETSTRUCT(oldtup);
+		Oid			oldprocid = oldproc->oid;
 		Datum		proargnames;
 		bool		isnull;
 		const char *dropcmd;
@@ -575,6 +577,26 @@ ProcedureCreate(const char *procedureName,
 			}
 		}
 
+		/*
+		 * Serialize replacement with any session recording a dependency on
+		 * this procedure's definition.
+		 */
+		LockDatabaseObject(ProcedureRelationId, oldprocid, 0,
+						   AccessExclusiveLock);
+		ReleaseSysCache(oldtup);
+		oldtup = SearchSysCache1(PROCOID, ObjectIdGetDatum(oldprocid));
+		if (!HeapTupleIsValid(oldtup))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("function \"%s\" was concurrently dropped",
+							procedureName)));
+		oldproc = (Form_pg_proc) GETSTRUCT(oldtup);
+
+		/* Ownership might have changed while we waited. */
+		if (!object_ownercheck(ProcedureRelationId, oldproc->oid, proowner))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
+						   procedureName);
+
 		/*
 		 * Do not change existing oid, ownership or permissions, either.  Note
 		 * dependency-update code below has to agree with this decision.
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 3afd762e9dc..b41a8a72ea6 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -62,6 +62,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "pgstat.h"
+#include "storage/lmgr.h"
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
 #include "utils/acl.h"
@@ -1425,6 +1426,34 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 			elog(ERROR, "option \"%s\" not recognized", defel->defname);
 	}
 
+	/*
+	 * Serialize definition changes with any session recording a dependency on
+	 * this procedure's definition, then refetch the tuple before applying the
+	 * requested changes.
+	 */
+	heap_freetuple(tup);
+	LockDatabaseObject(ProcedureRelationId, funcOid, 0, AccessExclusiveLock);
+
+	tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("function \"%s\" was concurrently dropped",
+						NameListToString(stmt->func->objname))));
+
+	procForm = (Form_pg_proc) GETSTRUCT(tup);
+
+	/* Permission check: must own function */
+	if (!object_ownercheck(ProcedureRelationId, funcOid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, stmt->objtype,
+					   NameListToString(stmt->func->objname));
+
+	if (procForm->prokind == PROKIND_AGGREGATE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an aggregate function",
+						NameListToString(stmt->func->objname))));
+
 	if (volatility_item)
 		procForm->provolatile = interpret_func_volatility(volatility_item);
 	if (strict_item)
-- 
2.52.0

