New Object Access Type hooks

Started by Mark Dilgeralmost 4 years ago62 messages
#1Mark Dilger
mark.dilger@enterprisedb.com
2 attachment(s)

Hackers,

Over in [1], Joshua proposed a new set of Object Access Type hooks based on strings rather than Oids.

His patch was written to be applied atop my patch for granting privileges on gucs.

On review of his patch, I became uncomfortable with the complete lack of regression test coverage. To be fair, he did paste a bit of testing logic to the thread, but it appears to be based on pgaudit, and it is unclear how to include such a test in the core project, where pgaudit is not assumed to be installed.

First, I refactored his patch to work against HEAD and not depend on my GUCs patch. Find that as v1-0001. The refactoring exposed a bit of a problem. To call the new hook for SET and ALTER SYSTEM commands, I need to pass in the Oid of a catalog table. But since my GUC patch isn't applied yet, there isn't any such table (pg_setting_acl or whatnot) to pass. So I'm passing InvalidOid, but I don't know if that is right. In any event, if we want a new API like this, we should think a bit harder about whether it can be used to check operations where no table Oid is applicable.

Second, I added a new test directory, src/test/modules/test_oat_hooks, which includes a new loadable module with hook implementations and a regression test for testing the object access hooks. The main point of the test is to log which hooks get called in which order, and which hooks do or do not get called when other hooks allow or deny access. That information shows up in the expected output as NOTICE messages.

This second patch has gotten a little long, and I'd like another pair of eyes on this before spending a second day on the effort. Please note that this is a quick WIP patch in response to the patch Joshua posted earlier today. Sorry for sometimes missing function comments, etc. The goal, if this design seems acceptable, is to polish this, hopefully with Joshua's assistance, and get it committed *before* my GUCs patch, so that my patch can be rebased to use it. Otherwise, if this is rejected, I can continue on the GUC patch without this.

(FYI, I got a test failure from src/test/recovery/t/013_crash_restart.pl when testing v1-0001. I'm not sure yet what that is about.)

Attachments:

v1-0001-Add-String-object-access-hooks.patchapplication/octet-stream; name=v1-0001-Add-String-object-access-hooks.patch; x-unix-mode=0644Download
From f994037f851a8cff27c492433dd37030f6c5e15d Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Thu, 17 Mar 2022 19:30:09 -0700
Subject: [PATCH v1 1/2] Add String object access hooks

The first user of these will be the GUC access controls

Written by Joshua Brindle; refactored by Mark Dilger
---
 src/backend/catalog/objectaccess.c | 128 +++++++++++++++++++++++++++++
 src/backend/utils/misc/guc.c       |  17 ++++
 src/include/catalog/objectaccess.h |  70 +++++++++++++++-
 src/include/nodes/parsenodes.h     |   4 +-
 src/include/utils/acl.h            |   4 +-
 5 files changed, 220 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/objectaccess.c b/src/backend/catalog/objectaccess.c
index 549fac4539..72ad6e9a90 100644
--- a/src/backend/catalog/objectaccess.c
+++ b/src/backend/catalog/objectaccess.c
@@ -20,6 +20,8 @@
  * and logging plugins.
  */
 object_access_hook_type object_access_hook = NULL;
+object_access_hook_type_str object_access_hook_str = NULL;
+
 
 /*
  * RunObjectPostCreateHook
@@ -143,3 +145,129 @@ RunFunctionExecuteHook(Oid objectId)
 						   ProcedureRelationId, objectId, 0,
 						   NULL);
 }
+
+/* String versions */
+
+
+/*
+ * RunObjectPostCreateHook
+ *
+ * It is entrypoint of OAT_POST_CREATE event
+ */
+void
+RunObjectPostCreateHookStr(Oid classId, const char *objectName, int subId,
+						bool is_internal)
+{
+	ObjectAccessPostCreate pc_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&pc_arg, 0, sizeof(ObjectAccessPostCreate));
+	pc_arg.is_internal = is_internal;
+
+	(*object_access_hook_str) (OAT_POST_CREATE,
+						   classId, objectName, subId,
+						   (void *) &pc_arg);
+}
+
+/*
+ * RunObjectDropHook
+ *
+ * It is entrypoint of OAT_DROP event
+ */
+void
+RunObjectDropHookStr(Oid classId, const char *objectName, int subId,
+				  int dropflags)
+{
+	ObjectAccessDrop drop_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&drop_arg, 0, sizeof(ObjectAccessDrop));
+	drop_arg.dropflags = dropflags;
+
+	(*object_access_hook_str) (OAT_DROP,
+						   classId, objectName, subId,
+						   (void *) &drop_arg);
+}
+
+/*
+ * RunObjectTruncateHook
+ *
+ * It is the entrypoint of OAT_TRUNCATE event
+ */
+void
+RunObjectTruncateHookStr(const char *objectName)
+{
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	(*object_access_hook_str) (OAT_TRUNCATE,
+						   RelationRelationId, objectName, 0,
+						   NULL);
+}
+
+/*
+ * RunObjectPostAlterHook
+ *
+ * It is entrypoint of OAT_POST_ALTER event
+ */
+void
+RunObjectPostAlterHookStr(Oid classId, const char *objectName, int subId,
+					   Oid auxiliaryId, bool is_internal)
+{
+	ObjectAccessPostAlter pa_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&pa_arg, 0, sizeof(ObjectAccessPostAlter));
+	pa_arg.auxiliary_id = auxiliaryId;
+	pa_arg.is_internal = is_internal;
+
+	(*object_access_hook_str) (OAT_POST_ALTER,
+						   classId, objectName, subId,
+						   (void *) &pa_arg);
+}
+
+/*
+ * RunNamespaceSearchHook
+ *
+ * It is entrypoint of OAT_NAMESPACE_SEARCH event
+ */
+bool
+RunNamespaceSearchHookStr(const char *objectName, bool ereport_on_violation)
+{
+	ObjectAccessNamespaceSearch ns_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&ns_arg, 0, sizeof(ObjectAccessNamespaceSearch));
+	ns_arg.ereport_on_violation = ereport_on_violation;
+	ns_arg.result = true;
+
+	(*object_access_hook_str) (OAT_NAMESPACE_SEARCH,
+						   NamespaceRelationId, objectName, 0,
+						   (void *) &ns_arg);
+
+	return ns_arg.result;
+}
+
+/*
+ * RunFunctionExecuteHook
+ *
+ * It is entrypoint of OAT_FUNCTION_EXECUTE event
+ */
+void
+RunFunctionExecuteHookStr(const char *objectName)
+{
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	(*object_access_hook_str) (OAT_FUNCTION_EXECUTE,
+						   ProcedureRelationId, objectName, 0,
+						   NULL);
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e7f0a380e6..932aefc777 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -43,6 +43,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
@@ -8736,6 +8737,18 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 		replace_auto_config_value(&head, &tail, name, value);
 	}
 
+	/*
+	 * Invoke the post-alter hook for altering this GUC variable.
+	 *
+	 * We do this here rather than at the end, because ALTER SYSTEM is not
+	 * transactional.  If the hook aborts our transaction, it will be cleaner
+	 * to do so before we touch any files.
+	 */
+	InvokeObjectPostAlterHookArgStr(InvalidOid, name,
+									ACL_ALTER_SYSTEM,
+									altersysstmt->setstmt->kind,
+									false);
+
 	/*
 	 * To ensure crash safety, first write the new file data to a temp file,
 	 * then atomically rename it into place.
@@ -8907,6 +8920,10 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 			ResetAllOptions();
 			break;
 	}
+
+	/* Invoke the post-alter hook for setting this GUC variable. */
+	InvokeObjectPostAlterHookArgStr(InvalidOid, stmt->name,
+									ACL_SET_VALUE, stmt->kind, false);
 }
 
 /*
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index 508dfd0a6b..4d54ae2a7d 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -121,15 +121,23 @@ typedef struct
 	bool		result;
 } ObjectAccessNamespaceSearch;
 
-/* Plugin provides a hook function matching this signature. */
+/* Plugin provides a hook function matching one or both of these signatures. */
 typedef void (*object_access_hook_type) (ObjectAccessType access,
 										 Oid classId,
 										 Oid objectId,
 										 int subId,
 										 void *arg);
 
+typedef void (*object_access_hook_type_str) (ObjectAccessType access,
+										 Oid classId,
+										 const char *objectStr,
+										 int subId,
+										 void *arg);
+
 /* Plugin sets this variable to a suitable hook function. */
 extern PGDLLIMPORT object_access_hook_type object_access_hook;
+extern PGDLLIMPORT object_access_hook_type_str object_access_hook_str;
+
 
 /* Core code uses these functions to call the hook (see macros below). */
 extern void RunObjectPostCreateHook(Oid classId, Oid objectId, int subId,
@@ -142,6 +150,18 @@ extern void RunObjectPostAlterHook(Oid classId, Oid objectId, int subId,
 extern bool RunNamespaceSearchHook(Oid objectId, bool ereport_on_violation);
 extern void RunFunctionExecuteHook(Oid objectId);
 
+/* String versions */
+extern void RunObjectPostCreateHookStr(Oid classId, const char *objectStr, int subId,
+									bool is_internal);
+extern void RunObjectDropHookStr(Oid classId, const char *objectStr, int subId,
+							  int dropflags);
+extern void RunObjectTruncateHookStr(const char *objectStr);
+extern void RunObjectPostAlterHookStr(Oid classId, const char *objectStr, int subId,
+								   Oid auxiliaryId, bool is_internal);
+extern bool RunNamespaceSearchHookStr(const char *objectStr, bool ereport_on_violation);
+extern void RunFunctionExecuteHookStr(const char *objectStr);
+
+
 /*
  * The following macros are wrappers around the functions above; these should
  * normally be used to invoke the hook in lieu of calling the above functions
@@ -194,4 +214,52 @@ extern void RunFunctionExecuteHook(Oid objectId);
 			RunFunctionExecuteHook(objectId);	\
 	} while(0)
 
+
+#define InvokeObjectPostCreateHookStr(classId,objectName,subId)			\
+	InvokeObjectPostCreateHookArgStr((classId),(objectName),(subId),false)
+#define InvokeObjectPostCreateHookArgStr(classId,objectName,subId,is_internal) \
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectPostCreateHookStr((classId),(objectName),(subId),	\
+									(is_internal));					\
+	} while(0)
+
+#define InvokeObjectDropHookStr(classId,objectName,subId)				\
+	InvokeObjectDropHookArgStr((classId),(objectName),(subId),0)
+#define InvokeObjectDropHookArgStr(classId,objectName,subId,dropflags)	\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectDropHookStr((classId),(objectName),(subId),			\
+							  (dropflags));							\
+	} while(0)
+
+#define InvokeObjectTruncateHookStr(objectName)							\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectTruncateHookStr(objectName);						\
+	} while(0)
+
+#define InvokeObjectPostAlterHookStr(className,objectName,subId)			\
+	InvokeObjectPostAlterHookArgStr((classId),(objectName),(subId),		\
+								 InvalidOid,false)
+#define InvokeObjectPostAlterHookArgStr(classId,objectName,subId,		\
+									 auxiliaryId,is_internal)		\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectPostAlterHookStr((classId),(objectName),(subId),	\
+								   (auxiliaryId),(is_internal));	\
+	} while(0)
+
+#define InvokeNamespaceSearchHookStr(objectName, ereport_on_violation)	\
+	(!object_access_hook_str										\
+	 ? true															\
+	 : RunNamespaceSearchHookStr((objectName), (ereport_on_violation)))
+
+#define InvokeFunctionExecuteHookStr(objectName)		\
+	do {										\
+		if (object_access_hook_str)					\
+			RunFunctionExecuteHookStr(objectName);	\
+	} while(0)
+
+
 #endif							/* OBJECTACCESS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1617702d9d..dc361d62e2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -92,7 +92,9 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_SET_VALUE	(1<<12) /* for configuration parameters */
+#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
+#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 1ce4c5556e..91ce3d8e9c 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -146,9 +146,11 @@ typedef struct ArrayType Acl;
 #define ACL_CREATE_CHR			'C'
 #define ACL_CREATE_TEMP_CHR		'T'
 #define ACL_CONNECT_CHR			'c'
+#define ACL_SET_VALUE_CHR		's'
+#define ACL_ALTER_SYSTEM_CHR	'A'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcsA"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
-- 
2.35.1

v1-0002-Add-regression-tests-of-Object-Access-Type-hooks.patchapplication/octet-stream; name=v1-0002-Add-regression-tests-of-Object-Access-Type-hooks.patch; x-unix-mode=0644Download
From 77b7d000b750e6103c8af2769e9187e59d854af8 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Thu, 17 Mar 2022 15:05:18 -0700
Subject: [PATCH v1 2/2] Add regression tests of Object Access Type hooks

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/test_oat_hooks/.gitignore    |   4 +
 src/test/modules/test_oat_hooks/Makefile      |  27 +
 src/test/modules/test_oat_hooks/README        |  16 +
 .../expected/test_oat_hooks.out               | 213 ++++
 .../modules/test_oat_hooks/oat_hooks.conf     |   1 +
 .../test_oat_hooks/sql/test_oat_hooks.sql     |  59 ++
 .../modules/test_oat_hooks/test_oat_hooks.c   | 935 ++++++++++++++++++
 .../test_oat_hooks/test_oat_hooks.control     |   4 +
 .../modules/test_oat_hooks/test_oat_hooks.h   |  25 +
 10 files changed, 1285 insertions(+)
 create mode 100644 src/test/modules/test_oat_hooks/.gitignore
 create mode 100644 src/test/modules/test_oat_hooks/Makefile
 create mode 100644 src/test/modules/test_oat_hooks/README
 create mode 100644 src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
 create mode 100644 src/test/modules/test_oat_hooks/oat_hooks.conf
 create mode 100644 src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
 create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.c
 create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.control
 create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.h

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index dffc79b2d9..9090226daa 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -20,6 +20,7 @@ SUBDIRS = \
 		  test_ginpostinglist \
 		  test_integerset \
 		  test_misc \
+		  test_oat_hooks \
 		  test_parser \
 		  test_pg_dump \
 		  test_predtest \
diff --git a/src/test/modules/test_oat_hooks/.gitignore b/src/test/modules/test_oat_hooks/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_oat_hooks/Makefile b/src/test/modules/test_oat_hooks/Makefile
new file mode 100644
index 0000000000..ff249a6d91
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/Makefile
@@ -0,0 +1,27 @@
+# src/test/modules/test_oat_hooks/Makefile
+
+MODULE_big = test_oat_hooks
+OBJS = \
+	$(WIN32RES) \
+	test_oat_hooks.o
+PGFILEDESC = "test_oat_hooks - example use of object access hooks"
+
+EXTENSION = test_oat_hooks
+# DATA = test_oat_hooks--1.0.sql
+
+REGRESS = test_oat_hooks
+REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_oat_hooks/oat_hooks.conf
+# Disabled because these tests require "shared_preload_libraries=test_oat_hooks",
+# which typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_oat_hooks
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_oat_hooks/README b/src/test/modules/test_oat_hooks/README
new file mode 100644
index 0000000000..015deb25d4
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/README
@@ -0,0 +1,16 @@
+test_oat_hooks is an example of how to use the object access hooks to
+enforce mandatory access controls.
+
+Functions
+=========
+test_rls_hooks_permissive(CmdType cmdtype, Relation relation)
+    RETURNS List*
+
+Returns a list of policies which should be added to any existing
+policies on the relation, combined with OR.
+
+test_rls_hooks_restrictive(CmdType cmdtype, Relation relation)
+    RETURNS List*
+
+Returns a list of policies which should be added to any existing
+policies on the relation, combined with AND.
diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
new file mode 100644
index 0000000000..b0e3fe6e68
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
@@ -0,0 +1,213 @@
+LOAD 'test_oat_hooks';
+-- Turn on logging messages to see audit messages
+SET client_min_messages = 'LOG';
+-- SET commands fire both the ProcessUtility_hook and the
+-- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
+-- initial "attempting" audit message from the ProcessUtility_hook, but we
+-- should thereafter see the audit messages
+SET test_oat_hooks.audit = true;
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.audit]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.audit]
+NOTICE:  in process utility: superuser finished set
+-- Create objects for use in the test
+CREATE USER regress_test_user;
+NOTICE:  in process utility: superuser attempting CreateRoleStmt
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in process utility: superuser finished CreateRoleStmt
+CREATE TABLE regress_test_table (t text);
+NOTICE:  in process utility: superuser attempting CreateStmt
+NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: CREATE TABLE regress_test_table (t text);
+                     ^
+NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: CREATE TABLE regress_test_table (t text);
+                     ^
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0) [internal]
+NOTICE:  in object access: superuser attempting create (subId=0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0) [internal]
+NOTICE:  in process utility: superuser finished CreateStmt
+GRANT SELECT ON Table regress_test_table TO public;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
+	SELECT $1;
+$$ LANGUAGE sql;
+NOTICE:  in process utility: superuser attempting CreateFunctionStmt
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in process utility: superuser finished CreateFunctionStmt
+GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Do a few things as superuser
+SELECT * FROM regress_test_table;
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+-- Do those same things as non-superuser
+SET SESSION AUTHORIZATION regress_test_user;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: non-superuser finished set
+SELECT * FROM regress_test_table;
+NOTICE:  in object access: non-superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in object access: non-superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in executor check perms: non-superuser attempting execute
+NOTICE:  in executor check perms: non-superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: non-superuser attempting execute
+NOTICE:  in executor check perms: non-superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: non-superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: non-superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  must be superuser to execute ALTER SYSTEM command
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  must be superuser to execute ALTER SYSTEM command
+RESET SESSION AUTHORIZATION;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: superuser finished set
+-- Turn off non-superuser permissions
+SET test_oat_hooks.deny_set_variable = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_alter_system = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_object_access = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_object_access]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_object_access]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_exec_perms = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_utility_commands = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_utility_commands]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_utility_commands]
+NOTICE:  in process utility: superuser finished set
+-- Try again as non-superuser with permisisons denied
+SET SESSION AUTHORIZATION regress_test_user;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
+ERROR:  permission denied: set session_authorization
+SELECT * FROM regress_test_table;
+NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+RESET SESSION AUTHORIZATION;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.audit = false;
+NOTICE:  in process utility: superuser attempting set
+RESET client_min_messages;
diff --git a/src/test/modules/test_oat_hooks/oat_hooks.conf b/src/test/modules/test_oat_hooks/oat_hooks.conf
new file mode 100644
index 0000000000..a44cbdd4a4
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/oat_hooks.conf
@@ -0,0 +1 @@
+shared_preload_libraries = test_oat_hooks
diff --git a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
new file mode 100644
index 0000000000..86addbd670
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
@@ -0,0 +1,59 @@
+LOAD 'test_oat_hooks';
+
+-- Turn on logging messages to see audit messages
+SET client_min_messages = 'LOG';
+
+-- SET commands fire both the ProcessUtility_hook and the
+-- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
+-- initial "attempting" audit message from the ProcessUtility_hook, but we
+-- should thereafter see the audit messages
+SET test_oat_hooks.audit = true;
+
+-- Create objects for use in the test
+CREATE USER regress_test_user;
+CREATE TABLE regress_test_table (t text);
+GRANT SELECT ON Table regress_test_table TO public;
+CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
+	SELECT $1;
+$$ LANGUAGE sql;
+GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public;
+
+-- Do a few things as superuser
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+
+-- Do those same things as non-superuser
+SET SESSION AUTHORIZATION regress_test_user;
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+RESET SESSION AUTHORIZATION;
+
+-- Turn off non-superuser permissions
+SET test_oat_hooks.deny_set_variable = true;
+SET test_oat_hooks.deny_alter_system = true;
+SET test_oat_hooks.deny_object_access = true;
+SET test_oat_hooks.deny_exec_perms = true;
+SET test_oat_hooks.deny_utility_commands = true;
+
+-- Try again as non-superuser with permisisons denied
+SET SESSION AUTHORIZATION regress_test_user;
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+
+RESET SESSION AUTHORIZATION;
+
+SET test_oat_hooks.audit = false;
+
+RESET client_min_messages;
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
new file mode 100644
index 0000000000..177be98a71
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -0,0 +1,935 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_oat_hooks.c
+ *		Code for testing mandatory access control (MAC) using object access hooks.
+ *
+ * Copyright (c) 2015-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_oat_hooks/test_oat_hooks.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/dependency.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "executor/executor.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "tcop/utility.h"
+#include "test_oat_hooks.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * GUCs controlling which operations to deny
+ */
+static bool REGRESS_deny_set_variable = false;
+static bool REGRESS_deny_alter_system = false;
+static bool REGRESS_deny_object_access = false;
+static bool REGRESS_deny_exec_perms = false;
+static bool REGRESS_deny_utility_commands = false;
+static bool REGRESS_audit = false;
+
+/* Saved hook values in case of unload */
+static object_access_hook_type next_object_access_hook = NULL;
+static object_access_hook_type_str next_object_access_hook_str = NULL;
+static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+
+/* Test Object Access Type Hook hooks */
+static void REGRESS_object_access_hook_str(ObjectAccessType access,
+										   Oid classId, const char *objName,
+										   int subId, void *arg);
+static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
+									   Oid objectId, int subId, void *arg);
+static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static void REGRESS_utility_command(PlannedStmt *pstmt,
+									const char *queryString, bool readOnlyTree,
+									ProcessUtilityContext context,
+									ParamListInfo params,
+									QueryEnvironment *queryEnv,
+									DestReceiver *dest, QueryCompletion *qc);
+
+/* Helper functions */
+static const char *nodetag_to_string(NodeTag tag);
+static char *accesstype_to_string(ObjectAccessType access, int subId);
+static char *accesstype_arg_to_string(ObjectAccessType access, void *arg);
+
+
+void		_PG_init(void);
+void		_PG_fini(void);
+
+/*
+ * Module load/unload callback
+ */
+void
+_PG_init(void)
+{
+	/*
+	 * We allow to load the Object Access Type test module on single-user-mode
+	 * or shared_preload_libraries settings only.
+	 */
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("test_oat_hooks must be loaded via shared_preload_libraries")));
+
+	/*
+	 * test_oat_hooks.deny_set_variable = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_set_variable",
+							 "Deny non-superuser set permissions",
+							 NULL,
+							 &REGRESS_deny_set_variable,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_alter_system = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_alter_system",
+							 "Deny non-superuser alter system set permissions",
+							 NULL,
+							 &REGRESS_deny_alter_system,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_object_access = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_object_access",
+							 "Deny non-superuser object access permissions",
+							 NULL,
+							 &REGRESS_deny_object_access,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_exec_perms = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms",
+							 "Deny non-superuser exec permissions",
+							 NULL,
+							 &REGRESS_deny_exec_perms,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_utility_commands = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands",
+							 "Deny non-superuser utility commands",
+							 NULL,
+							 &REGRESS_deny_utility_commands,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.audit = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.audit",
+							 "Turn on/off debug audit messages",
+							 NULL,
+							 &REGRESS_audit,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	MarkGUCPrefixReserved("test_oat_hooks");
+
+	/* Object access hook */
+	next_object_access_hook = object_access_hook;
+	object_access_hook = REGRESS_object_access_hook;
+
+	/* Object access hook str */
+	next_object_access_hook_str = object_access_hook_str;
+	object_access_hook_str = REGRESS_object_access_hook_str;
+
+	/* DML permission check */
+	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+
+	/* ProcessUtility hook */
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = REGRESS_utility_command;
+}
+
+void
+_PG_fini(void)
+{
+	/* Unload hooks */
+	if (object_access_hook == REGRESS_object_access_hook)
+		object_access_hook = next_object_access_hook;
+
+	if (object_access_hook_str == REGRESS_object_access_hook_str)
+		object_access_hook_str = next_object_access_hook_str;
+
+	if (ExecutorCheckPerms_hook == REGRESS_exec_check_perms)
+		ExecutorCheckPerms_hook = next_exec_check_perms_hook;
+
+	if (ProcessUtility_hook == REGRESS_utility_command)
+		ProcessUtility_hook = next_ProcessUtility_hook;
+}
+
+static void
+emit_audit_message(const char *type, const char *hook, char *action, char *objName)
+{
+	if (REGRESS_audit)
+	{
+		const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
+
+		if (objName)
+			ereport(NOTICE,
+					(errcode(ERRCODE_INTERNAL_ERROR),
+					 errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
+		else
+			ereport(NOTICE,
+					(errcode(ERRCODE_INTERNAL_ERROR),
+					 errmsg("in %s: %s %s %s", hook, who, type, action)));
+	}
+
+	if (action)
+		pfree(action);
+	if (objName)
+		pfree(objName);
+}
+
+static void
+audit_attempt(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("attempting", hook, action, objName);
+}
+
+static void
+audit_success(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("finished", hook, action, objName);
+}
+
+static void
+audit_failure(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("denied", hook, action, objName);
+}
+
+static void
+REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
+{
+	audit_attempt("object_access_hook_str",
+				  accesstype_to_string(access, subId),
+				  pstrdup(objName));
+
+	if (next_object_access_hook_str)
+	{
+		(*next_object_access_hook_str)(access, classId, objName, subId, arg);
+	}
+
+	switch (access)
+	{
+		case OAT_POST_ALTER:
+			if (subId & ACL_SET_VALUE)
+			{
+				if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("permission denied: set %s", objName)));
+			}
+			else if (subId & ACL_ALTER_SYSTEM)
+			{
+				if (REGRESS_deny_alter_system && !superuser_arg(GetUserId()))
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("permission denied: alter system set %s", objName)));
+			}
+			else
+				elog(ERROR, "Unknown SettingAclRelationId subId: %d", subId);
+			break;
+		default:
+			break;
+	}
+
+	audit_success("object_access_hook_str",
+				  accesstype_to_string(access, subId),
+				  pstrdup(objName));
+}
+
+static void
+REGRESS_object_access_hook (ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
+{
+	audit_attempt("object access",
+				  accesstype_to_string(access, 0),
+				  accesstype_arg_to_string(access, arg));
+
+	if (REGRESS_deny_object_access && !superuser_arg(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s [%s]",
+						accesstype_to_string(access, 0),
+						accesstype_arg_to_string(access, arg))));
+
+	/* Forward to next hook in the chain */
+	if (next_object_access_hook)
+		(*next_object_access_hook)(access, classId, objectId, subId, arg);
+
+	audit_success("object access",
+				  accesstype_to_string(access, 0),
+				  accesstype_arg_to_string(access, arg));
+}
+
+static bool
+REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+{
+	bool		am_super = superuser_arg(GetUserId());
+	bool		allow = true;
+
+	audit_attempt("executor check perms", pstrdup("execute"), NULL);
+
+	/* Perform our check */
+	allow = !REGRESS_deny_exec_perms || am_super;
+	if (do_abort && !allow)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s", "execute")));
+
+	/* Forward to next hook in the chain */
+	if (next_exec_check_perms_hook &&
+		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		allow = false;
+
+	if (allow)
+		audit_success("executor check perms",
+					  pstrdup("execute"),
+					  NULL);
+	else
+		audit_failure("executor check perms",
+					  pstrdup("execute"),
+					  NULL);
+
+	return allow;
+}
+
+static void
+REGRESS_utility_command(PlannedStmt *pstmt,
+					  const char *queryString,
+					  bool readOnlyTree,
+					  ProcessUtilityContext context,
+					  ParamListInfo params,
+					  QueryEnvironment *queryEnv,
+					  DestReceiver *dest,
+					  QueryCompletion *qc)
+{
+	Node	   *parsetree = pstmt->utilityStmt;
+
+	const char *action;
+	NodeTag tag = nodeTag(parsetree);
+
+	switch (tag)
+	{
+		case T_VariableSetStmt:
+			action = "set";
+			break;
+		case T_AlterSystemStmt:
+			action = "alter system";
+			break;
+		case T_LoadStmt:
+			action = "load";
+			break;
+		default:
+			action = nodetag_to_string(tag);
+			break;
+	}
+
+	audit_attempt("process utility",
+				  pstrdup(action),
+				  NULL);
+
+	/* Check permissions */
+	if (REGRESS_deny_utility_commands && !superuser_arg(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s", action)));
+
+	/* Forward to next hook in the chain */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
+									 context, params, queryEnv,
+									 dest, qc);
+	else
+		standard_ProcessUtility(pstmt, queryString, readOnlyTree,
+								context, params, queryEnv,
+								dest, qc);
+
+	/* We're done */
+	audit_success("process utility",
+				  pstrdup(action),
+				  NULL);
+}
+
+static const char *
+nodetag_to_string(NodeTag tag)
+{
+	switch (tag)
+	{
+		case T_Invalid: return "Invalid"; break;
+		case T_IndexInfo: return "IndexInfo"; break;
+		case T_ExprContext: return "ExprContext"; break;
+		case T_ProjectionInfo: return "ProjectionInfo"; break;
+		case T_JunkFilter: return "JunkFilter"; break;
+		case T_OnConflictSetState: return "OnConflictSetState"; break;
+		case T_ResultRelInfo: return "ResultRelInfo"; break;
+		case T_EState: return "EState"; break;
+		case T_TupleTableSlot: return "TupleTableSlot"; break;
+		case T_Plan: return "Plan"; break;
+		case T_Result: return "Result"; break;
+		case T_ProjectSet: return "ProjectSet"; break;
+		case T_ModifyTable: return "ModifyTable"; break;
+		case T_Append: return "Append"; break;
+		case T_MergeAppend: return "MergeAppend"; break;
+		case T_RecursiveUnion: return "RecursiveUnion"; break;
+		case T_BitmapAnd: return "BitmapAnd"; break;
+		case T_BitmapOr: return "BitmapOr"; break;
+		case T_Scan: return "Scan"; break;
+		case T_SeqScan: return "SeqScan"; break;
+		case T_SampleScan: return "SampleScan"; break;
+		case T_IndexScan: return "IndexScan"; break;
+		case T_IndexOnlyScan: return "IndexOnlyScan"; break;
+		case T_BitmapIndexScan: return "BitmapIndexScan"; break;
+		case T_BitmapHeapScan: return "BitmapHeapScan"; break;
+		case T_TidScan: return "TidScan"; break;
+		case T_TidRangeScan: return "TidRangeScan"; break;
+		case T_SubqueryScan: return "SubqueryScan"; break;
+		case T_FunctionScan: return "FunctionScan"; break;
+		case T_ValuesScan: return "ValuesScan"; break;
+		case T_TableFuncScan: return "TableFuncScan"; break;
+		case T_CteScan: return "CteScan"; break;
+		case T_NamedTuplestoreScan: return "NamedTuplestoreScan"; break;
+		case T_WorkTableScan: return "WorkTableScan"; break;
+		case T_ForeignScan: return "ForeignScan"; break;
+		case T_CustomScan: return "CustomScan"; break;
+		case T_Join: return "Join"; break;
+		case T_NestLoop: return "NestLoop"; break;
+		case T_MergeJoin: return "MergeJoin"; break;
+		case T_HashJoin: return "HashJoin"; break;
+		case T_Material: return "Material"; break;
+		case T_Memoize: return "Memoize"; break;
+		case T_Sort: return "Sort"; break;
+		case T_IncrementalSort: return "IncrementalSort"; break;
+		case T_Group: return "Group"; break;
+		case T_Agg: return "Agg"; break;
+		case T_WindowAgg: return "WindowAgg"; break;
+		case T_Unique: return "Unique"; break;
+		case T_Gather: return "Gather"; break;
+		case T_GatherMerge: return "GatherMerge"; break;
+		case T_Hash: return "Hash"; break;
+		case T_SetOp: return "SetOp"; break;
+		case T_LockRows: return "LockRows"; break;
+		case T_Limit: return "Limit"; break;
+		case T_NestLoopParam: return "NestLoopParam"; break;
+		case T_PlanRowMark: return "PlanRowMark"; break;
+		case T_PartitionPruneInfo: return "PartitionPruneInfo"; break;
+		case T_PartitionedRelPruneInfo: return "PartitionedRelPruneInfo"; break;
+		case T_PartitionPruneStepOp: return "PartitionPruneStepOp"; break;
+		case T_PartitionPruneStepCombine: return "PartitionPruneStepCombine"; break;
+		case T_PlanInvalItem: return "PlanInvalItem"; break;
+		case T_PlanState: return "PlanState"; break;
+		case T_ResultState: return "ResultState"; break;
+		case T_ProjectSetState: return "ProjectSetState"; break;
+		case T_ModifyTableState: return "ModifyTableState"; break;
+		case T_AppendState: return "AppendState"; break;
+		case T_MergeAppendState: return "MergeAppendState"; break;
+		case T_RecursiveUnionState: return "RecursiveUnionState"; break;
+		case T_BitmapAndState: return "BitmapAndState"; break;
+		case T_BitmapOrState: return "BitmapOrState"; break;
+		case T_ScanState: return "ScanState"; break;
+		case T_SeqScanState: return "SeqScanState"; break;
+		case T_SampleScanState: return "SampleScanState"; break;
+		case T_IndexScanState: return "IndexScanState"; break;
+		case T_IndexOnlyScanState: return "IndexOnlyScanState"; break;
+		case T_BitmapIndexScanState: return "BitmapIndexScanState"; break;
+		case T_BitmapHeapScanState: return "BitmapHeapScanState"; break;
+		case T_TidScanState: return "TidScanState"; break;
+		case T_TidRangeScanState: return "TidRangeScanState"; break;
+		case T_SubqueryScanState: return "SubqueryScanState"; break;
+		case T_FunctionScanState: return "FunctionScanState"; break;
+		case T_TableFuncScanState: return "TableFuncScanState"; break;
+		case T_ValuesScanState: return "ValuesScanState"; break;
+		case T_CteScanState: return "CteScanState"; break;
+		case T_NamedTuplestoreScanState: return "NamedTuplestoreScanState"; break;
+		case T_WorkTableScanState: return "WorkTableScanState"; break;
+		case T_ForeignScanState: return "ForeignScanState"; break;
+		case T_CustomScanState: return "CustomScanState"; break;
+		case T_JoinState: return "JoinState"; break;
+		case T_NestLoopState: return "NestLoopState"; break;
+		case T_MergeJoinState: return "MergeJoinState"; break;
+		case T_HashJoinState: return "HashJoinState"; break;
+		case T_MaterialState: return "MaterialState"; break;
+		case T_MemoizeState: return "MemoizeState"; break;
+		case T_SortState: return "SortState"; break;
+		case T_IncrementalSortState: return "IncrementalSortState"; break;
+		case T_GroupState: return "GroupState"; break;
+		case T_AggState: return "AggState"; break;
+		case T_WindowAggState: return "WindowAggState"; break;
+		case T_UniqueState: return "UniqueState"; break;
+		case T_GatherState: return "GatherState"; break;
+		case T_GatherMergeState: return "GatherMergeState"; break;
+		case T_HashState: return "HashState"; break;
+		case T_SetOpState: return "SetOpState"; break;
+		case T_LockRowsState: return "LockRowsState"; break;
+		case T_LimitState: return "LimitState"; break;
+		case T_Alias: return "Alias"; break;
+		case T_RangeVar: return "RangeVar"; break;
+		case T_TableFunc: return "TableFunc"; break;
+		case T_Var: return "Var"; break;
+		case T_Const: return "Const"; break;
+		case T_Param: return "Param"; break;
+		case T_Aggref: return "Aggref"; break;
+		case T_GroupingFunc: return "GroupingFunc"; break;
+		case T_WindowFunc: return "WindowFunc"; break;
+		case T_SubscriptingRef: return "SubscriptingRef"; break;
+		case T_FuncExpr: return "FuncExpr"; break;
+		case T_NamedArgExpr: return "NamedArgExpr"; break;
+		case T_OpExpr: return "OpExpr"; break;
+		case T_DistinctExpr: return "DistinctExpr"; break;
+		case T_NullIfExpr: return "NullIfExpr"; break;
+		case T_ScalarArrayOpExpr: return "ScalarArrayOpExpr"; break;
+		case T_BoolExpr: return "BoolExpr"; break;
+		case T_SubLink: return "SubLink"; break;
+		case T_SubPlan: return "SubPlan"; break;
+		case T_AlternativeSubPlan: return "AlternativeSubPlan"; break;
+		case T_FieldSelect: return "FieldSelect"; break;
+		case T_FieldStore: return "FieldStore"; break;
+		case T_RelabelType: return "RelabelType"; break;
+		case T_CoerceViaIO: return "CoerceViaIO"; break;
+		case T_ArrayCoerceExpr: return "ArrayCoerceExpr"; break;
+		case T_ConvertRowtypeExpr: return "ConvertRowtypeExpr"; break;
+		case T_CollateExpr: return "CollateExpr"; break;
+		case T_CaseExpr: return "CaseExpr"; break;
+		case T_CaseWhen: return "CaseWhen"; break;
+		case T_CaseTestExpr: return "CaseTestExpr"; break;
+		case T_ArrayExpr: return "ArrayExpr"; break;
+		case T_RowExpr: return "RowExpr"; break;
+		case T_RowCompareExpr: return "RowCompareExpr"; break;
+		case T_CoalesceExpr: return "CoalesceExpr"; break;
+		case T_MinMaxExpr: return "MinMaxExpr"; break;
+		case T_SQLValueFunction: return "SQLValueFunction"; break;
+		case T_XmlExpr: return "XmlExpr"; break;
+		case T_NullTest: return "NullTest"; break;
+		case T_BooleanTest: return "BooleanTest"; break;
+		case T_CoerceToDomain: return "CoerceToDomain"; break;
+		case T_CoerceToDomainValue: return "CoerceToDomainValue"; break;
+		case T_SetToDefault: return "SetToDefault"; break;
+		case T_CurrentOfExpr: return "CurrentOfExpr"; break;
+		case T_NextValueExpr: return "NextValueExpr"; break;
+		case T_InferenceElem: return "InferenceElem"; break;
+		case T_TargetEntry: return "TargetEntry"; break;
+		case T_RangeTblRef: return "RangeTblRef"; break;
+		case T_JoinExpr: return "JoinExpr"; break;
+		case T_FromExpr: return "FromExpr"; break;
+		case T_OnConflictExpr: return "OnConflictExpr"; break;
+		case T_IntoClause: return "IntoClause"; break;
+		case T_ExprState: return "ExprState"; break;
+		case T_WindowFuncExprState: return "WindowFuncExprState"; break;
+		case T_SetExprState: return "SetExprState"; break;
+		case T_SubPlanState: return "SubPlanState"; break;
+		case T_DomainConstraintState: return "DomainConstraintState"; break;
+		case T_PlannerInfo: return "PlannerInfo"; break;
+		case T_PlannerGlobal: return "PlannerGlobal"; break;
+		case T_RelOptInfo: return "RelOptInfo"; break;
+		case T_IndexOptInfo: return "IndexOptInfo"; break;
+		case T_ForeignKeyOptInfo: return "ForeignKeyOptInfo"; break;
+		case T_ParamPathInfo: return "ParamPathInfo"; break;
+		case T_Path: return "Path"; break;
+		case T_IndexPath: return "IndexPath"; break;
+		case T_BitmapHeapPath: return "BitmapHeapPath"; break;
+		case T_BitmapAndPath: return "BitmapAndPath"; break;
+		case T_BitmapOrPath: return "BitmapOrPath"; break;
+		case T_TidPath: return "TidPath"; break;
+		case T_TidRangePath: return "TidRangePath"; break;
+		case T_SubqueryScanPath: return "SubqueryScanPath"; break;
+		case T_ForeignPath: return "ForeignPath"; break;
+		case T_CustomPath: return "CustomPath"; break;
+		case T_NestPath: return "NestPath"; break;
+		case T_MergePath: return "MergePath"; break;
+		case T_HashPath: return "HashPath"; break;
+		case T_AppendPath: return "AppendPath"; break;
+		case T_MergeAppendPath: return "MergeAppendPath"; break;
+		case T_GroupResultPath: return "GroupResultPath"; break;
+		case T_MaterialPath: return "MaterialPath"; break;
+		case T_MemoizePath: return "MemoizePath"; break;
+		case T_UniquePath: return "UniquePath"; break;
+		case T_GatherPath: return "GatherPath"; break;
+		case T_GatherMergePath: return "GatherMergePath"; break;
+		case T_ProjectionPath: return "ProjectionPath"; break;
+		case T_ProjectSetPath: return "ProjectSetPath"; break;
+		case T_SortPath: return "SortPath"; break;
+		case T_IncrementalSortPath: return "IncrementalSortPath"; break;
+		case T_GroupPath: return "GroupPath"; break;
+		case T_UpperUniquePath: return "UpperUniquePath"; break;
+		case T_AggPath: return "AggPath"; break;
+		case T_GroupingSetsPath: return "GroupingSetsPath"; break;
+		case T_MinMaxAggPath: return "MinMaxAggPath"; break;
+		case T_WindowAggPath: return "WindowAggPath"; break;
+		case T_SetOpPath: return "SetOpPath"; break;
+		case T_RecursiveUnionPath: return "RecursiveUnionPath"; break;
+		case T_LockRowsPath: return "LockRowsPath"; break;
+		case T_ModifyTablePath: return "ModifyTablePath"; break;
+		case T_LimitPath: return "LimitPath"; break;
+		case T_EquivalenceClass: return "EquivalenceClass"; break;
+		case T_EquivalenceMember: return "EquivalenceMember"; break;
+		case T_PathKey: return "PathKey"; break;
+		case T_PathTarget: return "PathTarget"; break;
+		case T_RestrictInfo: return "RestrictInfo"; break;
+		case T_IndexClause: return "IndexClause"; break;
+		case T_PlaceHolderVar: return "PlaceHolderVar"; break;
+		case T_SpecialJoinInfo: return "SpecialJoinInfo"; break;
+		case T_AppendRelInfo: return "AppendRelInfo"; break;
+		case T_RowIdentityVarInfo: return "RowIdentityVarInfo"; break;
+		case T_PlaceHolderInfo: return "PlaceHolderInfo"; break;
+		case T_MinMaxAggInfo: return "MinMaxAggInfo"; break;
+		case T_PlannerParamItem: return "PlannerParamItem"; break;
+		case T_RollupData: return "RollupData"; break;
+		case T_GroupingSetData: return "GroupingSetData"; break;
+		case T_StatisticExtInfo: return "StatisticExtInfo"; break;
+		case T_AllocSetContext: return "AllocSetContext"; break;
+		case T_SlabContext: return "SlabContext"; break;
+		case T_GenerationContext: return "GenerationContext"; break;
+		case T_Integer: return "Integer"; break;
+		case T_Float: return "Float"; break;
+		case T_Boolean: return "Boolean"; break;
+		case T_String: return "String"; break;
+		case T_BitString: return "BitString"; break;
+		case T_List: return "List"; break;
+		case T_IntList: return "IntList"; break;
+		case T_OidList: return "OidList"; break;
+		case T_ExtensibleNode: return "ExtensibleNode"; break;
+		case T_RawStmt: return "RawStmt"; break;
+		case T_Query: return "Query"; break;
+		case T_PlannedStmt: return "PlannedStmt"; break;
+		case T_InsertStmt: return "InsertStmt"; break;
+		case T_DeleteStmt: return "DeleteStmt"; break;
+		case T_UpdateStmt: return "UpdateStmt"; break;
+		case T_SelectStmt: return "SelectStmt"; break;
+		case T_ReturnStmt: return "ReturnStmt"; break;
+		case T_PLAssignStmt: return "PLAssignStmt"; break;
+		case T_AlterTableStmt: return "AlterTableStmt"; break;
+		case T_AlterTableCmd: return "AlterTableCmd"; break;
+		case T_AlterDomainStmt: return "AlterDomainStmt"; break;
+		case T_SetOperationStmt: return "SetOperationStmt"; break;
+		case T_GrantStmt: return "GrantStmt"; break;
+		case T_GrantRoleStmt: return "GrantRoleStmt"; break;
+		case T_AlterDefaultPrivilegesStmt: return "AlterDefaultPrivilegesStmt"; break;
+		case T_ClosePortalStmt: return "ClosePortalStmt"; break;
+		case T_ClusterStmt: return "ClusterStmt"; break;
+		case T_CopyStmt: return "CopyStmt"; break;
+		case T_CreateStmt: return "CreateStmt"; break;
+		case T_DefineStmt: return "DefineStmt"; break;
+		case T_DropStmt: return "DropStmt"; break;
+		case T_TruncateStmt: return "TruncateStmt"; break;
+		case T_CommentStmt: return "CommentStmt"; break;
+		case T_FetchStmt: return "FetchStmt"; break;
+		case T_IndexStmt: return "IndexStmt"; break;
+		case T_CreateFunctionStmt: return "CreateFunctionStmt"; break;
+		case T_AlterFunctionStmt: return "AlterFunctionStmt"; break;
+		case T_DoStmt: return "DoStmt"; break;
+		case T_RenameStmt: return "RenameStmt"; break;
+		case T_RuleStmt: return "RuleStmt"; break;
+		case T_NotifyStmt: return "NotifyStmt"; break;
+		case T_ListenStmt: return "ListenStmt"; break;
+		case T_UnlistenStmt: return "UnlistenStmt"; break;
+		case T_TransactionStmt: return "TransactionStmt"; break;
+		case T_ViewStmt: return "ViewStmt"; break;
+		case T_LoadStmt: return "LoadStmt"; break;
+		case T_CreateDomainStmt: return "CreateDomainStmt"; break;
+		case T_CreatedbStmt: return "CreatedbStmt"; break;
+		case T_DropdbStmt: return "DropdbStmt"; break;
+		case T_VacuumStmt: return "VacuumStmt"; break;
+		case T_ExplainStmt: return "ExplainStmt"; break;
+		case T_CreateTableAsStmt: return "CreateTableAsStmt"; break;
+		case T_CreateSeqStmt: return "CreateSeqStmt"; break;
+		case T_AlterSeqStmt: return "AlterSeqStmt"; break;
+		case T_VariableSetStmt: return "VariableSetStmt"; break;
+		case T_VariableShowStmt: return "VariableShowStmt"; break;
+		case T_DiscardStmt: return "DiscardStmt"; break;
+		case T_CreateTrigStmt: return "CreateTrigStmt"; break;
+		case T_CreatePLangStmt: return "CreatePLangStmt"; break;
+		case T_CreateRoleStmt: return "CreateRoleStmt"; break;
+		case T_AlterRoleStmt: return "AlterRoleStmt"; break;
+		case T_DropRoleStmt: return "DropRoleStmt"; break;
+		case T_LockStmt: return "LockStmt"; break;
+		case T_ConstraintsSetStmt: return "ConstraintsSetStmt"; break;
+		case T_ReindexStmt: return "ReindexStmt"; break;
+		case T_CheckPointStmt: return "CheckPointStmt"; break;
+		case T_CreateSchemaStmt: return "CreateSchemaStmt"; break;
+		case T_AlterDatabaseStmt: return "AlterDatabaseStmt"; break;
+		case T_AlterDatabaseRefreshCollStmt: return "AlterDatabaseRefreshCollStmt"; break;
+		case T_AlterDatabaseSetStmt: return "AlterDatabaseSetStmt"; break;
+		case T_AlterRoleSetStmt: return "AlterRoleSetStmt"; break;
+		case T_CreateConversionStmt: return "CreateConversionStmt"; break;
+		case T_CreateCastStmt: return "CreateCastStmt"; break;
+		case T_CreateOpClassStmt: return "CreateOpClassStmt"; break;
+		case T_CreateOpFamilyStmt: return "CreateOpFamilyStmt"; break;
+		case T_AlterOpFamilyStmt: return "AlterOpFamilyStmt"; break;
+		case T_PrepareStmt: return "PrepareStmt"; break;
+		case T_ExecuteStmt: return "ExecuteStmt"; break;
+		case T_DeallocateStmt: return "DeallocateStmt"; break;
+		case T_DeclareCursorStmt: return "DeclareCursorStmt"; break;
+		case T_CreateTableSpaceStmt: return "CreateTableSpaceStmt"; break;
+		case T_DropTableSpaceStmt: return "DropTableSpaceStmt"; break;
+		case T_AlterObjectDependsStmt: return "AlterObjectDependsStmt"; break;
+		case T_AlterObjectSchemaStmt: return "AlterObjectSchemaStmt"; break;
+		case T_AlterOwnerStmt: return "AlterOwnerStmt"; break;
+		case T_AlterOperatorStmt: return "AlterOperatorStmt"; break;
+		case T_AlterTypeStmt: return "AlterTypeStmt"; break;
+		case T_DropOwnedStmt: return "DropOwnedStmt"; break;
+		case T_ReassignOwnedStmt: return "ReassignOwnedStmt"; break;
+		case T_CompositeTypeStmt: return "CompositeTypeStmt"; break;
+		case T_CreateEnumStmt: return "CreateEnumStmt"; break;
+		case T_CreateRangeStmt: return "CreateRangeStmt"; break;
+		case T_AlterEnumStmt: return "AlterEnumStmt"; break;
+		case T_AlterTSDictionaryStmt: return "AlterTSDictionaryStmt"; break;
+		case T_AlterTSConfigurationStmt: return "AlterTSConfigurationStmt"; break;
+		case T_CreateFdwStmt: return "CreateFdwStmt"; break;
+		case T_AlterFdwStmt: return "AlterFdwStmt"; break;
+		case T_CreateForeignServerStmt: return "CreateForeignServerStmt"; break;
+		case T_AlterForeignServerStmt: return "AlterForeignServerStmt"; break;
+		case T_CreateUserMappingStmt: return "CreateUserMappingStmt"; break;
+		case T_AlterUserMappingStmt: return "AlterUserMappingStmt"; break;
+		case T_DropUserMappingStmt: return "DropUserMappingStmt"; break;
+		case T_AlterTableSpaceOptionsStmt: return "AlterTableSpaceOptionsStmt"; break;
+		case T_AlterTableMoveAllStmt: return "AlterTableMoveAllStmt"; break;
+		case T_SecLabelStmt: return "SecLabelStmt"; break;
+		case T_CreateForeignTableStmt: return "CreateForeignTableStmt"; break;
+		case T_ImportForeignSchemaStmt: return "ImportForeignSchemaStmt"; break;
+		case T_CreateExtensionStmt: return "CreateExtensionStmt"; break;
+		case T_AlterExtensionStmt: return "AlterExtensionStmt"; break;
+		case T_AlterExtensionContentsStmt: return "AlterExtensionContentsStmt"; break;
+		case T_CreateEventTrigStmt: return "CreateEventTrigStmt"; break;
+		case T_AlterEventTrigStmt: return "AlterEventTrigStmt"; break;
+		case T_RefreshMatViewStmt: return "RefreshMatViewStmt"; break;
+		case T_ReplicaIdentityStmt: return "ReplicaIdentityStmt"; break;
+		case T_AlterSystemStmt: return "AlterSystemStmt"; break;
+		case T_CreatePolicyStmt: return "CreatePolicyStmt"; break;
+		case T_AlterPolicyStmt: return "AlterPolicyStmt"; break;
+		case T_CreateTransformStmt: return "CreateTransformStmt"; break;
+		case T_CreateAmStmt: return "CreateAmStmt"; break;
+		case T_CreatePublicationStmt: return "CreatePublicationStmt"; break;
+		case T_AlterPublicationStmt: return "AlterPublicationStmt"; break;
+		case T_CreateSubscriptionStmt: return "CreateSubscriptionStmt"; break;
+		case T_AlterSubscriptionStmt: return "AlterSubscriptionStmt"; break;
+		case T_DropSubscriptionStmt: return "DropSubscriptionStmt"; break;
+		case T_CreateStatsStmt: return "CreateStatsStmt"; break;
+		case T_AlterCollationStmt: return "AlterCollationStmt"; break;
+		case T_CallStmt: return "CallStmt"; break;
+		case T_AlterStatsStmt: return "AlterStatsStmt"; break;
+		case T_A_Expr: return "A_Expr"; break;
+		case T_ColumnRef: return "ColumnRef"; break;
+		case T_ParamRef: return "ParamRef"; break;
+		case T_A_Const: return "A_Const"; break;
+		case T_FuncCall: return "FuncCall"; break;
+		case T_A_Star: return "A_Star"; break;
+		case T_A_Indices: return "A_Indices"; break;
+		case T_A_Indirection: return "A_Indirection"; break;
+		case T_A_ArrayExpr: return "A_ArrayExpr"; break;
+		case T_ResTarget: return "ResTarget"; break;
+		case T_MultiAssignRef: return "MultiAssignRef"; break;
+		case T_TypeCast: return "TypeCast"; break;
+		case T_CollateClause: return "CollateClause"; break;
+		case T_SortBy: return "SortBy"; break;
+		case T_WindowDef: return "WindowDef"; break;
+		case T_RangeSubselect: return "RangeSubselect"; break;
+		case T_RangeFunction: return "RangeFunction"; break;
+		case T_RangeTableSample: return "RangeTableSample"; break;
+		case T_RangeTableFunc: return "RangeTableFunc"; break;
+		case T_RangeTableFuncCol: return "RangeTableFuncCol"; break;
+		case T_TypeName: return "TypeName"; break;
+		case T_ColumnDef: return "ColumnDef"; break;
+		case T_IndexElem: return "IndexElem"; break;
+		case T_StatsElem: return "StatsElem"; break;
+		case T_Constraint: return "Constraint"; break;
+		case T_DefElem: return "DefElem"; break;
+		case T_RangeTblEntry: return "RangeTblEntry"; break;
+		case T_RangeTblFunction: return "RangeTblFunction"; break;
+		case T_TableSampleClause: return "TableSampleClause"; break;
+		case T_WithCheckOption: return "WithCheckOption"; break;
+		case T_SortGroupClause: return "SortGroupClause"; break;
+		case T_GroupingSet: return "GroupingSet"; break;
+		case T_WindowClause: return "WindowClause"; break;
+		case T_ObjectWithArgs: return "ObjectWithArgs"; break;
+		case T_AccessPriv: return "AccessPriv"; break;
+		case T_CreateOpClassItem: return "CreateOpClassItem"; break;
+		case T_TableLikeClause: return "TableLikeClause"; break;
+		case T_FunctionParameter: return "FunctionParameter"; break;
+		case T_LockingClause: return "LockingClause"; break;
+		case T_RowMarkClause: return "RowMarkClause"; break;
+		case T_XmlSerialize: return "XmlSerialize"; break;
+		case T_WithClause: return "WithClause"; break;
+		case T_InferClause: return "InferClause"; break;
+		case T_OnConflictClause: return "OnConflictClause"; break;
+		case T_CTESearchClause: return "CTESearchClause"; break;
+		case T_CTECycleClause: return "CTECycleClause"; break;
+		case T_CommonTableExpr: return "CommonTableExpr"; break;
+		case T_RoleSpec: return "RoleSpec"; break;
+		case T_TriggerTransition: return "TriggerTransition"; break;
+		case T_PartitionElem: return "PartitionElem"; break;
+		case T_PartitionSpec: return "PartitionSpec"; break;
+		case T_PartitionBoundSpec: return "PartitionBoundSpec"; break;
+		case T_PartitionRangeDatum: return "PartitionRangeDatum"; break;
+		case T_PartitionCmd: return "PartitionCmd"; break;
+		case T_VacuumRelation: return "VacuumRelation"; break;
+		case T_PublicationObjSpec: return "PublicationObjSpec"; break;
+		case T_PublicationTable: return "PublicationTable"; break;
+		case T_IdentifySystemCmd: return "IdentifySystemCmd"; break;
+		case T_BaseBackupCmd: return "BaseBackupCmd"; break;
+		case T_CreateReplicationSlotCmd: return "CreateReplicationSlotCmd"; break;
+		case T_DropReplicationSlotCmd: return "DropReplicationSlotCmd"; break;
+		case T_ReadReplicationSlotCmd: return "ReadReplicationSlotCmd"; break;
+		case T_StartReplicationCmd: return "StartReplicationCmd"; break;
+		case T_TimeLineHistoryCmd: return "TimeLineHistoryCmd"; break;
+		case T_TriggerData: return "TriggerData"; break;
+		case T_EventTriggerData: return "EventTriggerData"; break;
+		case T_ReturnSetInfo: return "ReturnSetInfo"; break;
+		case T_WindowObjectData: return "WindowObjectData"; break;
+		case T_TIDBitmap: return "TIDBitmap"; break;
+		case T_InlineCodeBlock: return "InlineCodeBlock"; break;
+		case T_FdwRoutine: return "FdwRoutine"; break;
+		case T_IndexAmRoutine: return "IndexAmRoutine"; break;
+		case T_TableAmRoutine: return "TableAmRoutine"; break;
+		case T_TsmRoutine: return "TsmRoutine"; break;
+		case T_ForeignKeyCacheInfo: return "ForeignKeyCacheInfo"; break;
+		case T_CallContext: return "CallContext"; break;
+		case T_SupportRequestSimplify: return "SupportRequestSimplify"; break;
+		case T_SupportRequestSelectivity: return "SupportRequestSelectivity"; break;
+		case T_SupportRequestCost: return "SupportRequestCost"; break;
+		case T_SupportRequestRows: return "SupportRequestRows"; break;
+		case T_SupportRequestIndexCondition: return "SupportRequestIndexCondition"; break;
+		default:
+			break;
+	}
+	return "UNRECOGNIZED NodeTag";
+}
+
+static char *
+accesstype_to_string(ObjectAccessType access, int subId)
+{
+	const char *type;
+
+	switch (access)
+	{
+		case OAT_POST_CREATE:
+			type = "create";
+			break;
+		case OAT_DROP:
+			type = "drop";
+			break;
+		case OAT_POST_ALTER:
+			type = "alter";
+			break;
+		case OAT_NAMESPACE_SEARCH:
+			type = "namespace search";
+			break;
+		case OAT_FUNCTION_EXECUTE:
+			type = "execute";
+			break;
+		case OAT_TRUNCATE:
+			type = "truncate";
+			break;
+		default:
+			type = "UNRECOGNIZED ObjectAccessType";
+	}
+
+	if (subId & ACL_SET_VALUE)
+		return psprintf("%s (set)", type);
+	if (subId & ACL_ALTER_SYSTEM)
+		return psprintf("%s (alter system set)", type);
+
+	return  psprintf("%s (subId=%d)", type, subId);
+}
+
+static char *
+accesstype_arg_to_string(ObjectAccessType access, void *arg)
+{
+	if (arg == NULL)
+		return pstrdup("extra info null");
+
+	switch (access)
+	{
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *)arg;
+				return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
+			}
+			break;
+		case OAT_DROP:
+			{
+				ObjectAccessDrop *drop_arg = (ObjectAccessDrop *)arg;
+
+				return psprintf("%s%s%s%s%s%s",
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "internal action," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "concurrent drop," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "suppress notices," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "keep original object," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "keep extensions," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "normal concurrent drop," : ""));
+			}
+			break;
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter*)arg;
+
+				return psprintf("%s %s auxiliary object",
+					(pa_arg->is_internal ? "internal" : "explicit"),
+					(OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
+			}
+			break;
+		case OAT_NAMESPACE_SEARCH:
+			{
+				ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *)arg;
+
+				return psprintf("%s, %s",
+					(ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
+					(ns_arg->result ? "allowed" : "denied"));
+			}
+			break;
+		case OAT_TRUNCATE:
+		case OAT_FUNCTION_EXECUTE:
+			/* hook takes no arg. */
+			return pstrdup("unexpected extra info pointer received");
+		default:
+			return pstrdup("cannot parse extra info for unrecognized access type");
+	}
+
+	return pstrdup("unknown");
+}
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.control b/src/test/modules/test_oat_hooks/test_oat_hooks.control
new file mode 100644
index 0000000000..3d3cf4a8ac
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.control
@@ -0,0 +1,4 @@
+comment = 'Test code for Object Access hooks'
+default_version = '1.0'
+module_pathname = '$libdir/test_oat_hooks'
+relocatable = true
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.h b/src/test/modules/test_oat_hooks/test_oat_hooks.h
new file mode 100644
index 0000000000..ff1ee1bf5c
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.h
@@ -0,0 +1,25 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_rls_hooks.h
+ *		Definitions for OAT hooks
+ *
+ * Copyright (c) 2015-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_oat_hooks/test_oat_hooks.h
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#ifndef TEST_OAT_HOOKS_H
+#define TEST_OAT_HOOKS_H
+
+#include <rewrite/rowsecurity.h>
+
+/* Return set of permissive hooks based on CmdType and Relation */
+extern List *test_rls_hooks_permissive(CmdType cmdtype, Relation relation);
+
+/* Return set of restrictive hooks based on CmdType and Relation */
+extern List *test_rls_hooks_restrictive(CmdType cmdtype, Relation relation);
+
+#endif				/* TEST_OAT_HOOKS_H */
-- 
2.35.1

#2Joshua Brindle
joshua.brindle@crunchydata.com
In reply to: Mark Dilger (#1)
Re: New Object Access Type hooks

On Thu, Mar 17, 2022 at 11:21 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:

Hackers,

Over in [1], Joshua proposed a new set of Object Access Type hooks based on strings rather than Oids.

His patch was written to be applied atop my patch for granting privileges on gucs.

On review of his patch, I became uncomfortable with the complete lack of regression test coverage. To be fair, he did paste a bit of testing logic to the thread, but it appears to be based on pgaudit, and it is unclear how to include such a test in the core project, where pgaudit is not assumed to be installed.

First, I refactored his patch to work against HEAD and not depend on my GUCs patch. Find that as v1-0001. The refactoring exposed a bit of a problem. To call the new hook for SET and ALTER SYSTEM commands, I need to pass in the Oid of a catalog table. But since my GUC patch isn't applied yet, there isn't any such table (pg_setting_acl or whatnot) to pass. So I'm passing InvalidOid, but I don't know if that is right. In any event, if we want a new API like this, we should think a bit harder about whether it can be used to check operations where no table Oid is applicable.

Second, I added a new test directory, src/test/modules/test_oat_hooks, which includes a new loadable module with hook implementations and a regression test for testing the object access hooks. The main point of the test is to log which hooks get called in which order, and which hooks do or do not get called when other hooks allow or deny access. That information shows up in the expected output as NOTICE messages.

This second patch has gotten a little long, and I'd like another pair of eyes on this before spending a second day on the effort. Please note that this is a quick WIP patch in response to the patch Joshua posted earlier today. Sorry for sometimes missing function comments, etc. The goal, if this design seems acceptable, is to polish this, hopefully with Joshua's assistance, and get it committed *before* my GUCs patch, so that my patch can be rebased to use it. Otherwise, if this is rejected, I can continue on the GUC patch without this.

This is great, thank you for doing this. I didn't even realize the OAT
hooks had no regression tests.

It looks good to me, I reviewed both and tested the module. I wonder
if the slight abuse of subid is warranted with brand new hooks going
in but not enough to object, I just hope this doesn't rise to the too
large to merge this late level.

Show quoted text

(FYI, I got a test failure from src/test/recovery/t/013_crash_restart.pl when testing v1-0001. I'm not sure yet what that is about.)

[1] /messages/by-id/664799.1647456444@sss.pgh.pa.us

#3Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Joshua Brindle (#2)
Re: New Object Access Type hooks

On Mar 18, 2022, at 7:16 AM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:

This is great, thank you for doing this. I didn't even realize the OAT
hooks had no regression tests.

It looks good to me, I reviewed both and tested the module. I wonder
if the slight abuse of subid is warranted with brand new hooks going
in but not enough to object, I just hope this doesn't rise to the too
large to merge this late level.

The majority of the patch is regression testing code, stuff which doesn't get installed. It's even marked as NO_INSTALLCHECK, so it won't get installed even as part of "make installcheck". That seems safe enough to me.

Not including tests of OAT seems worse, as it leaves us open to breaking the behavior without realizing we've done so. A refactoring of the core code might cause hooks to be called in a different order, something which isn't necessarily wrong, but should not be done unknowingly.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#4Andrew Dunstan
andrew@dunslane.net
In reply to: Mark Dilger (#3)
Re: New Object Access Type hooks

On 3/18/22 11:15, Mark Dilger wrote:

On Mar 18, 2022, at 7:16 AM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:

This is great, thank you for doing this. I didn't even realize the OAT
hooks had no regression tests.

It looks good to me, I reviewed both and tested the module. I wonder
if the slight abuse of subid is warranted with brand new hooks going
in but not enough to object, I just hope this doesn't rise to the too
large to merge this late level.

The core code is extracted from a current CF patch, so I think in
principle it's OK.

I haven't looked at it in detail, but regarding the test code I'm not
sure why there's a .control file, since this isn't a loadable extension,
not why there's a test_oat_hooks.h file.

The majority of the patch is regression testing code, stuff which doesn't get installed. It's even marked as NO_INSTALLCHECK, so it won't get installed even as part of "make installcheck". That seems safe enough to me.

Not including tests of OAT seems worse, as it leaves us open to breaking the behavior without realizing we've done so. A refactoring of the core code might cause hooks to be called in a different order, something which isn't necessarily wrong, but should not be done unknowingly.

Yes, and in any case we've added test code after feature freeze in the past.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#5Andrew Dunstan
andrew@dunslane.net
In reply to: Mark Dilger (#1)
Re: New Object Access Type hooks

On 3/17/22 23:21, Mark Dilger wrote:

Hackers,

Over in [1], Joshua proposed a new set of Object Access Type hooks based on strings rather than Oids.

His patch was written to be applied atop my patch for granting privileges on gucs.

On review of his patch, I became uncomfortable with the complete lack of regression test coverage. To be fair, he did paste a bit of testing logic to the thread, but it appears to be based on pgaudit, and it is unclear how to include such a test in the core project, where pgaudit is not assumed to be installed.

First, I refactored his patch to work against HEAD and not depend on my GUCs patch. Find that as v1-0001. The refactoring exposed a bit of a problem. To call the new hook for SET and ALTER SYSTEM commands, I need to pass in the Oid of a catalog table. But since my GUC patch isn't applied yet, there isn't any such table (pg_setting_acl or whatnot) to pass. So I'm passing InvalidOid, but I don't know if that is right. In any event, if we want a new API like this, we should think a bit harder about whether it can be used to check operations where no table Oid is applicable.

My first inclination is to say it's probably ok. The immediately obvious
alternative would be to create yet another set of functions that don't
have classId parameters. That doesn't seem attractive.

Modulo that issue I think patch 1 is basically ok, but we should fix the
comments in objectaccess.c.  Rather than "It is [the] entrypoint ..." we
should have something like "Oid variant entrypoint ..." and "Name
variant entrypoint ...", and also fix the function names in the comments.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#6Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Andrew Dunstan (#4)
2 attachment(s)
Re: New Object Access Type hooks

On Mar 18, 2022, at 3:04 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

I haven't looked at it in detail, but regarding the test code I'm not
sure why there's a .control file, since this isn't a loadable extension,
not why there's a test_oat_hooks.h file.

The .control file exists because the test defines a loadable module which defines the hooks. The test_oat_hooks.h file was extraneous, and has been removed in v2.

Attachments:

v2-0001-Add-String-object-access-hooks.patchapplication/octet-stream; name=v2-0001-Add-String-object-access-hooks.patch; x-unix-mode=0644Download
From 4019f20cf0a62c66ccf26461bcb38720fc083386 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Thu, 17 Mar 2022 19:30:09 -0700
Subject: [PATCH v2 1/2] Add String object access hooks

The first user of these will be the GUC access controls

Written by Joshua Brindle; refactored by Mark Dilger
---
 src/backend/catalog/objectaccess.c | 128 +++++++++++++++++++++++++++++
 src/backend/utils/misc/guc.c       |  17 ++++
 src/include/catalog/objectaccess.h |  70 +++++++++++++++-
 src/include/nodes/parsenodes.h     |   4 +-
 src/include/utils/acl.h            |   4 +-
 5 files changed, 220 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/objectaccess.c b/src/backend/catalog/objectaccess.c
index 549fac4539..72ad6e9a90 100644
--- a/src/backend/catalog/objectaccess.c
+++ b/src/backend/catalog/objectaccess.c
@@ -20,6 +20,8 @@
  * and logging plugins.
  */
 object_access_hook_type object_access_hook = NULL;
+object_access_hook_type_str object_access_hook_str = NULL;
+
 
 /*
  * RunObjectPostCreateHook
@@ -143,3 +145,129 @@ RunFunctionExecuteHook(Oid objectId)
 						   ProcedureRelationId, objectId, 0,
 						   NULL);
 }
+
+/* String versions */
+
+
+/*
+ * RunObjectPostCreateHook
+ *
+ * It is entrypoint of OAT_POST_CREATE event
+ */
+void
+RunObjectPostCreateHookStr(Oid classId, const char *objectName, int subId,
+						bool is_internal)
+{
+	ObjectAccessPostCreate pc_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&pc_arg, 0, sizeof(ObjectAccessPostCreate));
+	pc_arg.is_internal = is_internal;
+
+	(*object_access_hook_str) (OAT_POST_CREATE,
+						   classId, objectName, subId,
+						   (void *) &pc_arg);
+}
+
+/*
+ * RunObjectDropHook
+ *
+ * It is entrypoint of OAT_DROP event
+ */
+void
+RunObjectDropHookStr(Oid classId, const char *objectName, int subId,
+				  int dropflags)
+{
+	ObjectAccessDrop drop_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&drop_arg, 0, sizeof(ObjectAccessDrop));
+	drop_arg.dropflags = dropflags;
+
+	(*object_access_hook_str) (OAT_DROP,
+						   classId, objectName, subId,
+						   (void *) &drop_arg);
+}
+
+/*
+ * RunObjectTruncateHook
+ *
+ * It is the entrypoint of OAT_TRUNCATE event
+ */
+void
+RunObjectTruncateHookStr(const char *objectName)
+{
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	(*object_access_hook_str) (OAT_TRUNCATE,
+						   RelationRelationId, objectName, 0,
+						   NULL);
+}
+
+/*
+ * RunObjectPostAlterHook
+ *
+ * It is entrypoint of OAT_POST_ALTER event
+ */
+void
+RunObjectPostAlterHookStr(Oid classId, const char *objectName, int subId,
+					   Oid auxiliaryId, bool is_internal)
+{
+	ObjectAccessPostAlter pa_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&pa_arg, 0, sizeof(ObjectAccessPostAlter));
+	pa_arg.auxiliary_id = auxiliaryId;
+	pa_arg.is_internal = is_internal;
+
+	(*object_access_hook_str) (OAT_POST_ALTER,
+						   classId, objectName, subId,
+						   (void *) &pa_arg);
+}
+
+/*
+ * RunNamespaceSearchHook
+ *
+ * It is entrypoint of OAT_NAMESPACE_SEARCH event
+ */
+bool
+RunNamespaceSearchHookStr(const char *objectName, bool ereport_on_violation)
+{
+	ObjectAccessNamespaceSearch ns_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&ns_arg, 0, sizeof(ObjectAccessNamespaceSearch));
+	ns_arg.ereport_on_violation = ereport_on_violation;
+	ns_arg.result = true;
+
+	(*object_access_hook_str) (OAT_NAMESPACE_SEARCH,
+						   NamespaceRelationId, objectName, 0,
+						   (void *) &ns_arg);
+
+	return ns_arg.result;
+}
+
+/*
+ * RunFunctionExecuteHook
+ *
+ * It is entrypoint of OAT_FUNCTION_EXECUTE event
+ */
+void
+RunFunctionExecuteHookStr(const char *objectName)
+{
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	(*object_access_hook_str) (OAT_FUNCTION_EXECUTE,
+						   ProcedureRelationId, objectName, 0,
+						   NULL);
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e7f0a380e6..932aefc777 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -43,6 +43,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
@@ -8736,6 +8737,18 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 		replace_auto_config_value(&head, &tail, name, value);
 	}
 
+	/*
+	 * Invoke the post-alter hook for altering this GUC variable.
+	 *
+	 * We do this here rather than at the end, because ALTER SYSTEM is not
+	 * transactional.  If the hook aborts our transaction, it will be cleaner
+	 * to do so before we touch any files.
+	 */
+	InvokeObjectPostAlterHookArgStr(InvalidOid, name,
+									ACL_ALTER_SYSTEM,
+									altersysstmt->setstmt->kind,
+									false);
+
 	/*
 	 * To ensure crash safety, first write the new file data to a temp file,
 	 * then atomically rename it into place.
@@ -8907,6 +8920,10 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 			ResetAllOptions();
 			break;
 	}
+
+	/* Invoke the post-alter hook for setting this GUC variable. */
+	InvokeObjectPostAlterHookArgStr(InvalidOid, stmt->name,
+									ACL_SET_VALUE, stmt->kind, false);
 }
 
 /*
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index 508dfd0a6b..4d54ae2a7d 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -121,15 +121,23 @@ typedef struct
 	bool		result;
 } ObjectAccessNamespaceSearch;
 
-/* Plugin provides a hook function matching this signature. */
+/* Plugin provides a hook function matching one or both of these signatures. */
 typedef void (*object_access_hook_type) (ObjectAccessType access,
 										 Oid classId,
 										 Oid objectId,
 										 int subId,
 										 void *arg);
 
+typedef void (*object_access_hook_type_str) (ObjectAccessType access,
+										 Oid classId,
+										 const char *objectStr,
+										 int subId,
+										 void *arg);
+
 /* Plugin sets this variable to a suitable hook function. */
 extern PGDLLIMPORT object_access_hook_type object_access_hook;
+extern PGDLLIMPORT object_access_hook_type_str object_access_hook_str;
+
 
 /* Core code uses these functions to call the hook (see macros below). */
 extern void RunObjectPostCreateHook(Oid classId, Oid objectId, int subId,
@@ -142,6 +150,18 @@ extern void RunObjectPostAlterHook(Oid classId, Oid objectId, int subId,
 extern bool RunNamespaceSearchHook(Oid objectId, bool ereport_on_violation);
 extern void RunFunctionExecuteHook(Oid objectId);
 
+/* String versions */
+extern void RunObjectPostCreateHookStr(Oid classId, const char *objectStr, int subId,
+									bool is_internal);
+extern void RunObjectDropHookStr(Oid classId, const char *objectStr, int subId,
+							  int dropflags);
+extern void RunObjectTruncateHookStr(const char *objectStr);
+extern void RunObjectPostAlterHookStr(Oid classId, const char *objectStr, int subId,
+								   Oid auxiliaryId, bool is_internal);
+extern bool RunNamespaceSearchHookStr(const char *objectStr, bool ereport_on_violation);
+extern void RunFunctionExecuteHookStr(const char *objectStr);
+
+
 /*
  * The following macros are wrappers around the functions above; these should
  * normally be used to invoke the hook in lieu of calling the above functions
@@ -194,4 +214,52 @@ extern void RunFunctionExecuteHook(Oid objectId);
 			RunFunctionExecuteHook(objectId);	\
 	} while(0)
 
+
+#define InvokeObjectPostCreateHookStr(classId,objectName,subId)			\
+	InvokeObjectPostCreateHookArgStr((classId),(objectName),(subId),false)
+#define InvokeObjectPostCreateHookArgStr(classId,objectName,subId,is_internal) \
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectPostCreateHookStr((classId),(objectName),(subId),	\
+									(is_internal));					\
+	} while(0)
+
+#define InvokeObjectDropHookStr(classId,objectName,subId)				\
+	InvokeObjectDropHookArgStr((classId),(objectName),(subId),0)
+#define InvokeObjectDropHookArgStr(classId,objectName,subId,dropflags)	\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectDropHookStr((classId),(objectName),(subId),			\
+							  (dropflags));							\
+	} while(0)
+
+#define InvokeObjectTruncateHookStr(objectName)							\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectTruncateHookStr(objectName);						\
+	} while(0)
+
+#define InvokeObjectPostAlterHookStr(className,objectName,subId)			\
+	InvokeObjectPostAlterHookArgStr((classId),(objectName),(subId),		\
+								 InvalidOid,false)
+#define InvokeObjectPostAlterHookArgStr(classId,objectName,subId,		\
+									 auxiliaryId,is_internal)		\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectPostAlterHookStr((classId),(objectName),(subId),	\
+								   (auxiliaryId),(is_internal));	\
+	} while(0)
+
+#define InvokeNamespaceSearchHookStr(objectName, ereport_on_violation)	\
+	(!object_access_hook_str										\
+	 ? true															\
+	 : RunNamespaceSearchHookStr((objectName), (ereport_on_violation)))
+
+#define InvokeFunctionExecuteHookStr(objectName)		\
+	do {										\
+		if (object_access_hook_str)					\
+			RunFunctionExecuteHookStr(objectName);	\
+	} while(0)
+
+
 #endif							/* OBJECTACCESS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1617702d9d..dc361d62e2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -92,7 +92,9 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_SET_VALUE	(1<<12) /* for configuration parameters */
+#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
+#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 1ce4c5556e..91ce3d8e9c 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -146,9 +146,11 @@ typedef struct ArrayType Acl;
 #define ACL_CREATE_CHR			'C'
 #define ACL_CREATE_TEMP_CHR		'T'
 #define ACL_CONNECT_CHR			'c'
+#define ACL_SET_VALUE_CHR		's'
+#define ACL_ALTER_SYSTEM_CHR	'A'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcsA"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
-- 
2.35.1

v2-0002-Add-regression-tests-of-Object-Access-Type-hooks.patchapplication/octet-stream; name=v2-0002-Add-regression-tests-of-Object-Access-Type-hooks.patch; x-unix-mode=0644Download
From f2dc1d71a894f12535269507f73133000197e81b Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Thu, 17 Mar 2022 15:05:18 -0700
Subject: [PATCH v2 2/2] Add regression tests of Object Access Type hooks

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/test_oat_hooks/.gitignore    |   4 +
 src/test/modules/test_oat_hooks/Makefile      |  27 +
 src/test/modules/test_oat_hooks/README        |  86 ++
 .../expected/test_oat_hooks.out               | 213 ++++
 .../test_oat_hooks/sql/test_oat_hooks.sql     |  59 ++
 .../modules/test_oat_hooks/test_oat_hooks.c   | 934 ++++++++++++++++++
 .../test_oat_hooks/test_oat_hooks.conf        |   1 +
 .../test_oat_hooks/test_oat_hooks.control     |   4 +
 9 files changed, 1329 insertions(+)
 create mode 100644 src/test/modules/test_oat_hooks/.gitignore
 create mode 100644 src/test/modules/test_oat_hooks/Makefile
 create mode 100644 src/test/modules/test_oat_hooks/README
 create mode 100644 src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
 create mode 100644 src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
 create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.c
 create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.conf
 create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.control

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index dffc79b2d9..9090226daa 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -20,6 +20,7 @@ SUBDIRS = \
 		  test_ginpostinglist \
 		  test_integerset \
 		  test_misc \
+		  test_oat_hooks \
 		  test_parser \
 		  test_pg_dump \
 		  test_predtest \
diff --git a/src/test/modules/test_oat_hooks/.gitignore b/src/test/modules/test_oat_hooks/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_oat_hooks/Makefile b/src/test/modules/test_oat_hooks/Makefile
new file mode 100644
index 0000000000..8313d66504
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/Makefile
@@ -0,0 +1,27 @@
+# src/test/modules/test_oat_hooks/Makefile
+
+MODULE_big = test_oat_hooks
+OBJS = \
+	$(WIN32RES) \
+	test_oat_hooks.o
+PGFILEDESC = "test_oat_hooks - example use of object access hooks"
+
+EXTENSION = test_oat_hooks
+# DATA = test_oat_hooks--1.0.sql
+
+REGRESS = test_oat_hooks
+REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_oat_hooks/test_oat_hooks.conf
+# Disabled because these tests require "shared_preload_libraries=test_oat_hooks",
+# which typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_oat_hooks
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_oat_hooks/README b/src/test/modules/test_oat_hooks/README
new file mode 100644
index 0000000000..a7c400c747
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/README
@@ -0,0 +1,86 @@
+OVERVIEW
+========
+
+This test module, "test_oat_hooks", is an example of how to use the object
+access hooks (OAT) to enforce mandatory access controls (MAC).
+
+The testing strategy is as follows:  When this module loads, it registers hooks
+of various types.  (See below.)  GUCs are defined to control each hook,
+determining whether the hook allows or denies actions for which it fires.  A
+single additional GUC controls the verbosity of the hooks.  GUCs default to
+permissive/quiet, which allows the module to load without generating noise in
+the log or denying any activity in the run-up to the regression test beginning.
+When the test begins, it uses SET commands to turn on logging and to control
+each hook's permissive/restrictive behavior.  Various SQL statements are run
+under both superuser and ordinary user permissions.  The output is compared
+against the expected output to verify that the hooks behaved and fired in the
+order by expect.
+
+Because users may care about the firing order of other system hooks relative to
+OAT hooks, ProcessUtility hooks and ExecutorCheckPerms hooks are also
+registered by this module, with their own logging and allow/deny behavior.
+
+
+SUSET test configuration GUCs
+=============================
+
+The following configuration parameters (GUCs) control this test module's Object
+Access Type (OAT), Process Utility and Executor Check Permissions hooks.  The
+general pattern is that each hook has a corresponding GUC which controls
+whether the hook will allow or deny operations for which the hook gets called.
+A real-world OAT hook should certainly provide more fine-grained conrol than
+merely "allow-all" vs. "deny-all", but for testing this is sufficient.
+
+Note that even when these hooks allow an action, the core permissions system
+may still refuse the action.  The firing order of the hooks relative to the
+core permissions system can be inferred from which NOTICE messages get emitted
+before an action is refused.
+
+Each hook applies the allow vs. deny setting to all operations performed by
+non-superusers.
+
+- test_oat_hooks.deny_set_variable
+
+  Controls whether the object_access_hook_str MAC function rejects attempts to
+  set a configuration parameter.
+
+- test_oat_hooks.deny_alter_system
+
+  Controls whether the object_access_hook_str MAC function rejects attempts to
+  alter system set a configuration parameter.
+
+- test_oat_hooks.deny_object_access
+
+  Controls whether the object_access_hook MAC function rejects all operations
+  for which it is called.
+
+- test_oat_hooks.deny_exec_perms
+
+  Controls whether the exec_check_perms MAC function rejects all operations for
+  which it is called.
+
+- test_oat_hooks.deny_utility_commands
+
+  Controls whether the ProcessUtility_hook function rejects all operations for
+  which it is called.
+
+- test_oat_hooks.audit
+
+  Controls whether each hook logs NOTICE messages for each attempt, along with
+  success or failure status.  Note that clearing or setting this GUC may itself
+  generate NOTICE messages appearing before but not after, or after but not
+  before, the new setting takes effect.
+
+
+Functions
+=========
+
+The module registers hooks by the following names:
+
+- REGRESS_object_access_hook
+
+- REGRESS_object_access_hook_str
+
+- REGRESS_exec_check_perms
+
+- REGRESS_utility_command
diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
new file mode 100644
index 0000000000..b0e3fe6e68
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
@@ -0,0 +1,213 @@
+LOAD 'test_oat_hooks';
+-- Turn on logging messages to see audit messages
+SET client_min_messages = 'LOG';
+-- SET commands fire both the ProcessUtility_hook and the
+-- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
+-- initial "attempting" audit message from the ProcessUtility_hook, but we
+-- should thereafter see the audit messages
+SET test_oat_hooks.audit = true;
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.audit]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.audit]
+NOTICE:  in process utility: superuser finished set
+-- Create objects for use in the test
+CREATE USER regress_test_user;
+NOTICE:  in process utility: superuser attempting CreateRoleStmt
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in process utility: superuser finished CreateRoleStmt
+CREATE TABLE regress_test_table (t text);
+NOTICE:  in process utility: superuser attempting CreateStmt
+NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: CREATE TABLE regress_test_table (t text);
+                     ^
+NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: CREATE TABLE regress_test_table (t text);
+                     ^
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0) [internal]
+NOTICE:  in object access: superuser attempting create (subId=0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0) [internal]
+NOTICE:  in process utility: superuser finished CreateStmt
+GRANT SELECT ON Table regress_test_table TO public;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
+	SELECT $1;
+$$ LANGUAGE sql;
+NOTICE:  in process utility: superuser attempting CreateFunctionStmt
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in process utility: superuser finished CreateFunctionStmt
+GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Do a few things as superuser
+SELECT * FROM regress_test_table;
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+-- Do those same things as non-superuser
+SET SESSION AUTHORIZATION regress_test_user;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: non-superuser finished set
+SELECT * FROM regress_test_table;
+NOTICE:  in object access: non-superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in object access: non-superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in executor check perms: non-superuser attempting execute
+NOTICE:  in executor check perms: non-superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: non-superuser attempting execute
+NOTICE:  in executor check perms: non-superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: non-superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: non-superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  must be superuser to execute ALTER SYSTEM command
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  must be superuser to execute ALTER SYSTEM command
+RESET SESSION AUTHORIZATION;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: superuser finished set
+-- Turn off non-superuser permissions
+SET test_oat_hooks.deny_set_variable = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_alter_system = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_object_access = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_object_access]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_object_access]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_exec_perms = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_utility_commands = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_utility_commands]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_utility_commands]
+NOTICE:  in process utility: superuser finished set
+-- Try again as non-superuser with permisisons denied
+SET SESSION AUTHORIZATION regress_test_user;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
+ERROR:  permission denied: set session_authorization
+SELECT * FROM regress_test_table;
+NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+RESET SESSION AUTHORIZATION;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.audit = false;
+NOTICE:  in process utility: superuser attempting set
+RESET client_min_messages;
diff --git a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
new file mode 100644
index 0000000000..86addbd670
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
@@ -0,0 +1,59 @@
+LOAD 'test_oat_hooks';
+
+-- Turn on logging messages to see audit messages
+SET client_min_messages = 'LOG';
+
+-- SET commands fire both the ProcessUtility_hook and the
+-- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
+-- initial "attempting" audit message from the ProcessUtility_hook, but we
+-- should thereafter see the audit messages
+SET test_oat_hooks.audit = true;
+
+-- Create objects for use in the test
+CREATE USER regress_test_user;
+CREATE TABLE regress_test_table (t text);
+GRANT SELECT ON Table regress_test_table TO public;
+CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
+	SELECT $1;
+$$ LANGUAGE sql;
+GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public;
+
+-- Do a few things as superuser
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+
+-- Do those same things as non-superuser
+SET SESSION AUTHORIZATION regress_test_user;
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+RESET SESSION AUTHORIZATION;
+
+-- Turn off non-superuser permissions
+SET test_oat_hooks.deny_set_variable = true;
+SET test_oat_hooks.deny_alter_system = true;
+SET test_oat_hooks.deny_object_access = true;
+SET test_oat_hooks.deny_exec_perms = true;
+SET test_oat_hooks.deny_utility_commands = true;
+
+-- Try again as non-superuser with permisisons denied
+SET SESSION AUTHORIZATION regress_test_user;
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+
+RESET SESSION AUTHORIZATION;
+
+SET test_oat_hooks.audit = false;
+
+RESET client_min_messages;
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
new file mode 100644
index 0000000000..b1709f4d63
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -0,0 +1,934 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_oat_hooks.c
+ *		Code for testing mandatory access control (MAC) using object access hooks.
+ *
+ * Copyright (c) 2015-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_oat_hooks/test_oat_hooks.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/dependency.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "executor/executor.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "tcop/utility.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * GUCs controlling which operations to deny
+ */
+static bool REGRESS_deny_set_variable = false;
+static bool REGRESS_deny_alter_system = false;
+static bool REGRESS_deny_object_access = false;
+static bool REGRESS_deny_exec_perms = false;
+static bool REGRESS_deny_utility_commands = false;
+static bool REGRESS_audit = false;
+
+/* Saved hook values in case of unload */
+static object_access_hook_type next_object_access_hook = NULL;
+static object_access_hook_type_str next_object_access_hook_str = NULL;
+static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+
+/* Test Object Access Type Hook hooks */
+static void REGRESS_object_access_hook_str(ObjectAccessType access,
+										   Oid classId, const char *objName,
+										   int subId, void *arg);
+static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
+									   Oid objectId, int subId, void *arg);
+static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static void REGRESS_utility_command(PlannedStmt *pstmt,
+									const char *queryString, bool readOnlyTree,
+									ProcessUtilityContext context,
+									ParamListInfo params,
+									QueryEnvironment *queryEnv,
+									DestReceiver *dest, QueryCompletion *qc);
+
+/* Helper functions */
+static const char *nodetag_to_string(NodeTag tag);
+static char *accesstype_to_string(ObjectAccessType access, int subId);
+static char *accesstype_arg_to_string(ObjectAccessType access, void *arg);
+
+
+void		_PG_init(void);
+void		_PG_fini(void);
+
+/*
+ * Module load/unload callback
+ */
+void
+_PG_init(void)
+{
+	/*
+	 * We allow to load the Object Access Type test module on single-user-mode
+	 * or shared_preload_libraries settings only.
+	 */
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("test_oat_hooks must be loaded via shared_preload_libraries")));
+
+	/*
+	 * test_oat_hooks.deny_set_variable = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_set_variable",
+							 "Deny non-superuser set permissions",
+							 NULL,
+							 &REGRESS_deny_set_variable,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_alter_system = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_alter_system",
+							 "Deny non-superuser alter system set permissions",
+							 NULL,
+							 &REGRESS_deny_alter_system,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_object_access = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_object_access",
+							 "Deny non-superuser object access permissions",
+							 NULL,
+							 &REGRESS_deny_object_access,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_exec_perms = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms",
+							 "Deny non-superuser exec permissions",
+							 NULL,
+							 &REGRESS_deny_exec_perms,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_utility_commands = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands",
+							 "Deny non-superuser utility commands",
+							 NULL,
+							 &REGRESS_deny_utility_commands,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.audit = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.audit",
+							 "Turn on/off debug audit messages",
+							 NULL,
+							 &REGRESS_audit,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	MarkGUCPrefixReserved("test_oat_hooks");
+
+	/* Object access hook */
+	next_object_access_hook = object_access_hook;
+	object_access_hook = REGRESS_object_access_hook;
+
+	/* Object access hook str */
+	next_object_access_hook_str = object_access_hook_str;
+	object_access_hook_str = REGRESS_object_access_hook_str;
+
+	/* DML permission check */
+	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+
+	/* ProcessUtility hook */
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = REGRESS_utility_command;
+}
+
+void
+_PG_fini(void)
+{
+	/* Unload hooks */
+	if (object_access_hook == REGRESS_object_access_hook)
+		object_access_hook = next_object_access_hook;
+
+	if (object_access_hook_str == REGRESS_object_access_hook_str)
+		object_access_hook_str = next_object_access_hook_str;
+
+	if (ExecutorCheckPerms_hook == REGRESS_exec_check_perms)
+		ExecutorCheckPerms_hook = next_exec_check_perms_hook;
+
+	if (ProcessUtility_hook == REGRESS_utility_command)
+		ProcessUtility_hook = next_ProcessUtility_hook;
+}
+
+static void
+emit_audit_message(const char *type, const char *hook, char *action, char *objName)
+{
+	if (REGRESS_audit)
+	{
+		const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
+
+		if (objName)
+			ereport(NOTICE,
+					(errcode(ERRCODE_INTERNAL_ERROR),
+					 errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
+		else
+			ereport(NOTICE,
+					(errcode(ERRCODE_INTERNAL_ERROR),
+					 errmsg("in %s: %s %s %s", hook, who, type, action)));
+	}
+
+	if (action)
+		pfree(action);
+	if (objName)
+		pfree(objName);
+}
+
+static void
+audit_attempt(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("attempting", hook, action, objName);
+}
+
+static void
+audit_success(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("finished", hook, action, objName);
+}
+
+static void
+audit_failure(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("denied", hook, action, objName);
+}
+
+static void
+REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
+{
+	audit_attempt("object_access_hook_str",
+				  accesstype_to_string(access, subId),
+				  pstrdup(objName));
+
+	if (next_object_access_hook_str)
+	{
+		(*next_object_access_hook_str)(access, classId, objName, subId, arg);
+	}
+
+	switch (access)
+	{
+		case OAT_POST_ALTER:
+			if (subId & ACL_SET_VALUE)
+			{
+				if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("permission denied: set %s", objName)));
+			}
+			else if (subId & ACL_ALTER_SYSTEM)
+			{
+				if (REGRESS_deny_alter_system && !superuser_arg(GetUserId()))
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("permission denied: alter system set %s", objName)));
+			}
+			else
+				elog(ERROR, "Unknown SettingAclRelationId subId: %d", subId);
+			break;
+		default:
+			break;
+	}
+
+	audit_success("object_access_hook_str",
+				  accesstype_to_string(access, subId),
+				  pstrdup(objName));
+}
+
+static void
+REGRESS_object_access_hook (ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
+{
+	audit_attempt("object access",
+				  accesstype_to_string(access, 0),
+				  accesstype_arg_to_string(access, arg));
+
+	if (REGRESS_deny_object_access && !superuser_arg(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s [%s]",
+						accesstype_to_string(access, 0),
+						accesstype_arg_to_string(access, arg))));
+
+	/* Forward to next hook in the chain */
+	if (next_object_access_hook)
+		(*next_object_access_hook)(access, classId, objectId, subId, arg);
+
+	audit_success("object access",
+				  accesstype_to_string(access, 0),
+				  accesstype_arg_to_string(access, arg));
+}
+
+static bool
+REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+{
+	bool		am_super = superuser_arg(GetUserId());
+	bool		allow = true;
+
+	audit_attempt("executor check perms", pstrdup("execute"), NULL);
+
+	/* Perform our check */
+	allow = !REGRESS_deny_exec_perms || am_super;
+	if (do_abort && !allow)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s", "execute")));
+
+	/* Forward to next hook in the chain */
+	if (next_exec_check_perms_hook &&
+		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		allow = false;
+
+	if (allow)
+		audit_success("executor check perms",
+					  pstrdup("execute"),
+					  NULL);
+	else
+		audit_failure("executor check perms",
+					  pstrdup("execute"),
+					  NULL);
+
+	return allow;
+}
+
+static void
+REGRESS_utility_command(PlannedStmt *pstmt,
+					  const char *queryString,
+					  bool readOnlyTree,
+					  ProcessUtilityContext context,
+					  ParamListInfo params,
+					  QueryEnvironment *queryEnv,
+					  DestReceiver *dest,
+					  QueryCompletion *qc)
+{
+	Node	   *parsetree = pstmt->utilityStmt;
+
+	const char *action;
+	NodeTag tag = nodeTag(parsetree);
+
+	switch (tag)
+	{
+		case T_VariableSetStmt:
+			action = "set";
+			break;
+		case T_AlterSystemStmt:
+			action = "alter system";
+			break;
+		case T_LoadStmt:
+			action = "load";
+			break;
+		default:
+			action = nodetag_to_string(tag);
+			break;
+	}
+
+	audit_attempt("process utility",
+				  pstrdup(action),
+				  NULL);
+
+	/* Check permissions */
+	if (REGRESS_deny_utility_commands && !superuser_arg(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s", action)));
+
+	/* Forward to next hook in the chain */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
+									 context, params, queryEnv,
+									 dest, qc);
+	else
+		standard_ProcessUtility(pstmt, queryString, readOnlyTree,
+								context, params, queryEnv,
+								dest, qc);
+
+	/* We're done */
+	audit_success("process utility",
+				  pstrdup(action),
+				  NULL);
+}
+
+static const char *
+nodetag_to_string(NodeTag tag)
+{
+	switch (tag)
+	{
+		case T_Invalid: return "Invalid"; break;
+		case T_IndexInfo: return "IndexInfo"; break;
+		case T_ExprContext: return "ExprContext"; break;
+		case T_ProjectionInfo: return "ProjectionInfo"; break;
+		case T_JunkFilter: return "JunkFilter"; break;
+		case T_OnConflictSetState: return "OnConflictSetState"; break;
+		case T_ResultRelInfo: return "ResultRelInfo"; break;
+		case T_EState: return "EState"; break;
+		case T_TupleTableSlot: return "TupleTableSlot"; break;
+		case T_Plan: return "Plan"; break;
+		case T_Result: return "Result"; break;
+		case T_ProjectSet: return "ProjectSet"; break;
+		case T_ModifyTable: return "ModifyTable"; break;
+		case T_Append: return "Append"; break;
+		case T_MergeAppend: return "MergeAppend"; break;
+		case T_RecursiveUnion: return "RecursiveUnion"; break;
+		case T_BitmapAnd: return "BitmapAnd"; break;
+		case T_BitmapOr: return "BitmapOr"; break;
+		case T_Scan: return "Scan"; break;
+		case T_SeqScan: return "SeqScan"; break;
+		case T_SampleScan: return "SampleScan"; break;
+		case T_IndexScan: return "IndexScan"; break;
+		case T_IndexOnlyScan: return "IndexOnlyScan"; break;
+		case T_BitmapIndexScan: return "BitmapIndexScan"; break;
+		case T_BitmapHeapScan: return "BitmapHeapScan"; break;
+		case T_TidScan: return "TidScan"; break;
+		case T_TidRangeScan: return "TidRangeScan"; break;
+		case T_SubqueryScan: return "SubqueryScan"; break;
+		case T_FunctionScan: return "FunctionScan"; break;
+		case T_ValuesScan: return "ValuesScan"; break;
+		case T_TableFuncScan: return "TableFuncScan"; break;
+		case T_CteScan: return "CteScan"; break;
+		case T_NamedTuplestoreScan: return "NamedTuplestoreScan"; break;
+		case T_WorkTableScan: return "WorkTableScan"; break;
+		case T_ForeignScan: return "ForeignScan"; break;
+		case T_CustomScan: return "CustomScan"; break;
+		case T_Join: return "Join"; break;
+		case T_NestLoop: return "NestLoop"; break;
+		case T_MergeJoin: return "MergeJoin"; break;
+		case T_HashJoin: return "HashJoin"; break;
+		case T_Material: return "Material"; break;
+		case T_Memoize: return "Memoize"; break;
+		case T_Sort: return "Sort"; break;
+		case T_IncrementalSort: return "IncrementalSort"; break;
+		case T_Group: return "Group"; break;
+		case T_Agg: return "Agg"; break;
+		case T_WindowAgg: return "WindowAgg"; break;
+		case T_Unique: return "Unique"; break;
+		case T_Gather: return "Gather"; break;
+		case T_GatherMerge: return "GatherMerge"; break;
+		case T_Hash: return "Hash"; break;
+		case T_SetOp: return "SetOp"; break;
+		case T_LockRows: return "LockRows"; break;
+		case T_Limit: return "Limit"; break;
+		case T_NestLoopParam: return "NestLoopParam"; break;
+		case T_PlanRowMark: return "PlanRowMark"; break;
+		case T_PartitionPruneInfo: return "PartitionPruneInfo"; break;
+		case T_PartitionedRelPruneInfo: return "PartitionedRelPruneInfo"; break;
+		case T_PartitionPruneStepOp: return "PartitionPruneStepOp"; break;
+		case T_PartitionPruneStepCombine: return "PartitionPruneStepCombine"; break;
+		case T_PlanInvalItem: return "PlanInvalItem"; break;
+		case T_PlanState: return "PlanState"; break;
+		case T_ResultState: return "ResultState"; break;
+		case T_ProjectSetState: return "ProjectSetState"; break;
+		case T_ModifyTableState: return "ModifyTableState"; break;
+		case T_AppendState: return "AppendState"; break;
+		case T_MergeAppendState: return "MergeAppendState"; break;
+		case T_RecursiveUnionState: return "RecursiveUnionState"; break;
+		case T_BitmapAndState: return "BitmapAndState"; break;
+		case T_BitmapOrState: return "BitmapOrState"; break;
+		case T_ScanState: return "ScanState"; break;
+		case T_SeqScanState: return "SeqScanState"; break;
+		case T_SampleScanState: return "SampleScanState"; break;
+		case T_IndexScanState: return "IndexScanState"; break;
+		case T_IndexOnlyScanState: return "IndexOnlyScanState"; break;
+		case T_BitmapIndexScanState: return "BitmapIndexScanState"; break;
+		case T_BitmapHeapScanState: return "BitmapHeapScanState"; break;
+		case T_TidScanState: return "TidScanState"; break;
+		case T_TidRangeScanState: return "TidRangeScanState"; break;
+		case T_SubqueryScanState: return "SubqueryScanState"; break;
+		case T_FunctionScanState: return "FunctionScanState"; break;
+		case T_TableFuncScanState: return "TableFuncScanState"; break;
+		case T_ValuesScanState: return "ValuesScanState"; break;
+		case T_CteScanState: return "CteScanState"; break;
+		case T_NamedTuplestoreScanState: return "NamedTuplestoreScanState"; break;
+		case T_WorkTableScanState: return "WorkTableScanState"; break;
+		case T_ForeignScanState: return "ForeignScanState"; break;
+		case T_CustomScanState: return "CustomScanState"; break;
+		case T_JoinState: return "JoinState"; break;
+		case T_NestLoopState: return "NestLoopState"; break;
+		case T_MergeJoinState: return "MergeJoinState"; break;
+		case T_HashJoinState: return "HashJoinState"; break;
+		case T_MaterialState: return "MaterialState"; break;
+		case T_MemoizeState: return "MemoizeState"; break;
+		case T_SortState: return "SortState"; break;
+		case T_IncrementalSortState: return "IncrementalSortState"; break;
+		case T_GroupState: return "GroupState"; break;
+		case T_AggState: return "AggState"; break;
+		case T_WindowAggState: return "WindowAggState"; break;
+		case T_UniqueState: return "UniqueState"; break;
+		case T_GatherState: return "GatherState"; break;
+		case T_GatherMergeState: return "GatherMergeState"; break;
+		case T_HashState: return "HashState"; break;
+		case T_SetOpState: return "SetOpState"; break;
+		case T_LockRowsState: return "LockRowsState"; break;
+		case T_LimitState: return "LimitState"; break;
+		case T_Alias: return "Alias"; break;
+		case T_RangeVar: return "RangeVar"; break;
+		case T_TableFunc: return "TableFunc"; break;
+		case T_Var: return "Var"; break;
+		case T_Const: return "Const"; break;
+		case T_Param: return "Param"; break;
+		case T_Aggref: return "Aggref"; break;
+		case T_GroupingFunc: return "GroupingFunc"; break;
+		case T_WindowFunc: return "WindowFunc"; break;
+		case T_SubscriptingRef: return "SubscriptingRef"; break;
+		case T_FuncExpr: return "FuncExpr"; break;
+		case T_NamedArgExpr: return "NamedArgExpr"; break;
+		case T_OpExpr: return "OpExpr"; break;
+		case T_DistinctExpr: return "DistinctExpr"; break;
+		case T_NullIfExpr: return "NullIfExpr"; break;
+		case T_ScalarArrayOpExpr: return "ScalarArrayOpExpr"; break;
+		case T_BoolExpr: return "BoolExpr"; break;
+		case T_SubLink: return "SubLink"; break;
+		case T_SubPlan: return "SubPlan"; break;
+		case T_AlternativeSubPlan: return "AlternativeSubPlan"; break;
+		case T_FieldSelect: return "FieldSelect"; break;
+		case T_FieldStore: return "FieldStore"; break;
+		case T_RelabelType: return "RelabelType"; break;
+		case T_CoerceViaIO: return "CoerceViaIO"; break;
+		case T_ArrayCoerceExpr: return "ArrayCoerceExpr"; break;
+		case T_ConvertRowtypeExpr: return "ConvertRowtypeExpr"; break;
+		case T_CollateExpr: return "CollateExpr"; break;
+		case T_CaseExpr: return "CaseExpr"; break;
+		case T_CaseWhen: return "CaseWhen"; break;
+		case T_CaseTestExpr: return "CaseTestExpr"; break;
+		case T_ArrayExpr: return "ArrayExpr"; break;
+		case T_RowExpr: return "RowExpr"; break;
+		case T_RowCompareExpr: return "RowCompareExpr"; break;
+		case T_CoalesceExpr: return "CoalesceExpr"; break;
+		case T_MinMaxExpr: return "MinMaxExpr"; break;
+		case T_SQLValueFunction: return "SQLValueFunction"; break;
+		case T_XmlExpr: return "XmlExpr"; break;
+		case T_NullTest: return "NullTest"; break;
+		case T_BooleanTest: return "BooleanTest"; break;
+		case T_CoerceToDomain: return "CoerceToDomain"; break;
+		case T_CoerceToDomainValue: return "CoerceToDomainValue"; break;
+		case T_SetToDefault: return "SetToDefault"; break;
+		case T_CurrentOfExpr: return "CurrentOfExpr"; break;
+		case T_NextValueExpr: return "NextValueExpr"; break;
+		case T_InferenceElem: return "InferenceElem"; break;
+		case T_TargetEntry: return "TargetEntry"; break;
+		case T_RangeTblRef: return "RangeTblRef"; break;
+		case T_JoinExpr: return "JoinExpr"; break;
+		case T_FromExpr: return "FromExpr"; break;
+		case T_OnConflictExpr: return "OnConflictExpr"; break;
+		case T_IntoClause: return "IntoClause"; break;
+		case T_ExprState: return "ExprState"; break;
+		case T_WindowFuncExprState: return "WindowFuncExprState"; break;
+		case T_SetExprState: return "SetExprState"; break;
+		case T_SubPlanState: return "SubPlanState"; break;
+		case T_DomainConstraintState: return "DomainConstraintState"; break;
+		case T_PlannerInfo: return "PlannerInfo"; break;
+		case T_PlannerGlobal: return "PlannerGlobal"; break;
+		case T_RelOptInfo: return "RelOptInfo"; break;
+		case T_IndexOptInfo: return "IndexOptInfo"; break;
+		case T_ForeignKeyOptInfo: return "ForeignKeyOptInfo"; break;
+		case T_ParamPathInfo: return "ParamPathInfo"; break;
+		case T_Path: return "Path"; break;
+		case T_IndexPath: return "IndexPath"; break;
+		case T_BitmapHeapPath: return "BitmapHeapPath"; break;
+		case T_BitmapAndPath: return "BitmapAndPath"; break;
+		case T_BitmapOrPath: return "BitmapOrPath"; break;
+		case T_TidPath: return "TidPath"; break;
+		case T_TidRangePath: return "TidRangePath"; break;
+		case T_SubqueryScanPath: return "SubqueryScanPath"; break;
+		case T_ForeignPath: return "ForeignPath"; break;
+		case T_CustomPath: return "CustomPath"; break;
+		case T_NestPath: return "NestPath"; break;
+		case T_MergePath: return "MergePath"; break;
+		case T_HashPath: return "HashPath"; break;
+		case T_AppendPath: return "AppendPath"; break;
+		case T_MergeAppendPath: return "MergeAppendPath"; break;
+		case T_GroupResultPath: return "GroupResultPath"; break;
+		case T_MaterialPath: return "MaterialPath"; break;
+		case T_MemoizePath: return "MemoizePath"; break;
+		case T_UniquePath: return "UniquePath"; break;
+		case T_GatherPath: return "GatherPath"; break;
+		case T_GatherMergePath: return "GatherMergePath"; break;
+		case T_ProjectionPath: return "ProjectionPath"; break;
+		case T_ProjectSetPath: return "ProjectSetPath"; break;
+		case T_SortPath: return "SortPath"; break;
+		case T_IncrementalSortPath: return "IncrementalSortPath"; break;
+		case T_GroupPath: return "GroupPath"; break;
+		case T_UpperUniquePath: return "UpperUniquePath"; break;
+		case T_AggPath: return "AggPath"; break;
+		case T_GroupingSetsPath: return "GroupingSetsPath"; break;
+		case T_MinMaxAggPath: return "MinMaxAggPath"; break;
+		case T_WindowAggPath: return "WindowAggPath"; break;
+		case T_SetOpPath: return "SetOpPath"; break;
+		case T_RecursiveUnionPath: return "RecursiveUnionPath"; break;
+		case T_LockRowsPath: return "LockRowsPath"; break;
+		case T_ModifyTablePath: return "ModifyTablePath"; break;
+		case T_LimitPath: return "LimitPath"; break;
+		case T_EquivalenceClass: return "EquivalenceClass"; break;
+		case T_EquivalenceMember: return "EquivalenceMember"; break;
+		case T_PathKey: return "PathKey"; break;
+		case T_PathTarget: return "PathTarget"; break;
+		case T_RestrictInfo: return "RestrictInfo"; break;
+		case T_IndexClause: return "IndexClause"; break;
+		case T_PlaceHolderVar: return "PlaceHolderVar"; break;
+		case T_SpecialJoinInfo: return "SpecialJoinInfo"; break;
+		case T_AppendRelInfo: return "AppendRelInfo"; break;
+		case T_RowIdentityVarInfo: return "RowIdentityVarInfo"; break;
+		case T_PlaceHolderInfo: return "PlaceHolderInfo"; break;
+		case T_MinMaxAggInfo: return "MinMaxAggInfo"; break;
+		case T_PlannerParamItem: return "PlannerParamItem"; break;
+		case T_RollupData: return "RollupData"; break;
+		case T_GroupingSetData: return "GroupingSetData"; break;
+		case T_StatisticExtInfo: return "StatisticExtInfo"; break;
+		case T_AllocSetContext: return "AllocSetContext"; break;
+		case T_SlabContext: return "SlabContext"; break;
+		case T_GenerationContext: return "GenerationContext"; break;
+		case T_Integer: return "Integer"; break;
+		case T_Float: return "Float"; break;
+		case T_Boolean: return "Boolean"; break;
+		case T_String: return "String"; break;
+		case T_BitString: return "BitString"; break;
+		case T_List: return "List"; break;
+		case T_IntList: return "IntList"; break;
+		case T_OidList: return "OidList"; break;
+		case T_ExtensibleNode: return "ExtensibleNode"; break;
+		case T_RawStmt: return "RawStmt"; break;
+		case T_Query: return "Query"; break;
+		case T_PlannedStmt: return "PlannedStmt"; break;
+		case T_InsertStmt: return "InsertStmt"; break;
+		case T_DeleteStmt: return "DeleteStmt"; break;
+		case T_UpdateStmt: return "UpdateStmt"; break;
+		case T_SelectStmt: return "SelectStmt"; break;
+		case T_ReturnStmt: return "ReturnStmt"; break;
+		case T_PLAssignStmt: return "PLAssignStmt"; break;
+		case T_AlterTableStmt: return "AlterTableStmt"; break;
+		case T_AlterTableCmd: return "AlterTableCmd"; break;
+		case T_AlterDomainStmt: return "AlterDomainStmt"; break;
+		case T_SetOperationStmt: return "SetOperationStmt"; break;
+		case T_GrantStmt: return "GrantStmt"; break;
+		case T_GrantRoleStmt: return "GrantRoleStmt"; break;
+		case T_AlterDefaultPrivilegesStmt: return "AlterDefaultPrivilegesStmt"; break;
+		case T_ClosePortalStmt: return "ClosePortalStmt"; break;
+		case T_ClusterStmt: return "ClusterStmt"; break;
+		case T_CopyStmt: return "CopyStmt"; break;
+		case T_CreateStmt: return "CreateStmt"; break;
+		case T_DefineStmt: return "DefineStmt"; break;
+		case T_DropStmt: return "DropStmt"; break;
+		case T_TruncateStmt: return "TruncateStmt"; break;
+		case T_CommentStmt: return "CommentStmt"; break;
+		case T_FetchStmt: return "FetchStmt"; break;
+		case T_IndexStmt: return "IndexStmt"; break;
+		case T_CreateFunctionStmt: return "CreateFunctionStmt"; break;
+		case T_AlterFunctionStmt: return "AlterFunctionStmt"; break;
+		case T_DoStmt: return "DoStmt"; break;
+		case T_RenameStmt: return "RenameStmt"; break;
+		case T_RuleStmt: return "RuleStmt"; break;
+		case T_NotifyStmt: return "NotifyStmt"; break;
+		case T_ListenStmt: return "ListenStmt"; break;
+		case T_UnlistenStmt: return "UnlistenStmt"; break;
+		case T_TransactionStmt: return "TransactionStmt"; break;
+		case T_ViewStmt: return "ViewStmt"; break;
+		case T_LoadStmt: return "LoadStmt"; break;
+		case T_CreateDomainStmt: return "CreateDomainStmt"; break;
+		case T_CreatedbStmt: return "CreatedbStmt"; break;
+		case T_DropdbStmt: return "DropdbStmt"; break;
+		case T_VacuumStmt: return "VacuumStmt"; break;
+		case T_ExplainStmt: return "ExplainStmt"; break;
+		case T_CreateTableAsStmt: return "CreateTableAsStmt"; break;
+		case T_CreateSeqStmt: return "CreateSeqStmt"; break;
+		case T_AlterSeqStmt: return "AlterSeqStmt"; break;
+		case T_VariableSetStmt: return "VariableSetStmt"; break;
+		case T_VariableShowStmt: return "VariableShowStmt"; break;
+		case T_DiscardStmt: return "DiscardStmt"; break;
+		case T_CreateTrigStmt: return "CreateTrigStmt"; break;
+		case T_CreatePLangStmt: return "CreatePLangStmt"; break;
+		case T_CreateRoleStmt: return "CreateRoleStmt"; break;
+		case T_AlterRoleStmt: return "AlterRoleStmt"; break;
+		case T_DropRoleStmt: return "DropRoleStmt"; break;
+		case T_LockStmt: return "LockStmt"; break;
+		case T_ConstraintsSetStmt: return "ConstraintsSetStmt"; break;
+		case T_ReindexStmt: return "ReindexStmt"; break;
+		case T_CheckPointStmt: return "CheckPointStmt"; break;
+		case T_CreateSchemaStmt: return "CreateSchemaStmt"; break;
+		case T_AlterDatabaseStmt: return "AlterDatabaseStmt"; break;
+		case T_AlterDatabaseRefreshCollStmt: return "AlterDatabaseRefreshCollStmt"; break;
+		case T_AlterDatabaseSetStmt: return "AlterDatabaseSetStmt"; break;
+		case T_AlterRoleSetStmt: return "AlterRoleSetStmt"; break;
+		case T_CreateConversionStmt: return "CreateConversionStmt"; break;
+		case T_CreateCastStmt: return "CreateCastStmt"; break;
+		case T_CreateOpClassStmt: return "CreateOpClassStmt"; break;
+		case T_CreateOpFamilyStmt: return "CreateOpFamilyStmt"; break;
+		case T_AlterOpFamilyStmt: return "AlterOpFamilyStmt"; break;
+		case T_PrepareStmt: return "PrepareStmt"; break;
+		case T_ExecuteStmt: return "ExecuteStmt"; break;
+		case T_DeallocateStmt: return "DeallocateStmt"; break;
+		case T_DeclareCursorStmt: return "DeclareCursorStmt"; break;
+		case T_CreateTableSpaceStmt: return "CreateTableSpaceStmt"; break;
+		case T_DropTableSpaceStmt: return "DropTableSpaceStmt"; break;
+		case T_AlterObjectDependsStmt: return "AlterObjectDependsStmt"; break;
+		case T_AlterObjectSchemaStmt: return "AlterObjectSchemaStmt"; break;
+		case T_AlterOwnerStmt: return "AlterOwnerStmt"; break;
+		case T_AlterOperatorStmt: return "AlterOperatorStmt"; break;
+		case T_AlterTypeStmt: return "AlterTypeStmt"; break;
+		case T_DropOwnedStmt: return "DropOwnedStmt"; break;
+		case T_ReassignOwnedStmt: return "ReassignOwnedStmt"; break;
+		case T_CompositeTypeStmt: return "CompositeTypeStmt"; break;
+		case T_CreateEnumStmt: return "CreateEnumStmt"; break;
+		case T_CreateRangeStmt: return "CreateRangeStmt"; break;
+		case T_AlterEnumStmt: return "AlterEnumStmt"; break;
+		case T_AlterTSDictionaryStmt: return "AlterTSDictionaryStmt"; break;
+		case T_AlterTSConfigurationStmt: return "AlterTSConfigurationStmt"; break;
+		case T_CreateFdwStmt: return "CreateFdwStmt"; break;
+		case T_AlterFdwStmt: return "AlterFdwStmt"; break;
+		case T_CreateForeignServerStmt: return "CreateForeignServerStmt"; break;
+		case T_AlterForeignServerStmt: return "AlterForeignServerStmt"; break;
+		case T_CreateUserMappingStmt: return "CreateUserMappingStmt"; break;
+		case T_AlterUserMappingStmt: return "AlterUserMappingStmt"; break;
+		case T_DropUserMappingStmt: return "DropUserMappingStmt"; break;
+		case T_AlterTableSpaceOptionsStmt: return "AlterTableSpaceOptionsStmt"; break;
+		case T_AlterTableMoveAllStmt: return "AlterTableMoveAllStmt"; break;
+		case T_SecLabelStmt: return "SecLabelStmt"; break;
+		case T_CreateForeignTableStmt: return "CreateForeignTableStmt"; break;
+		case T_ImportForeignSchemaStmt: return "ImportForeignSchemaStmt"; break;
+		case T_CreateExtensionStmt: return "CreateExtensionStmt"; break;
+		case T_AlterExtensionStmt: return "AlterExtensionStmt"; break;
+		case T_AlterExtensionContentsStmt: return "AlterExtensionContentsStmt"; break;
+		case T_CreateEventTrigStmt: return "CreateEventTrigStmt"; break;
+		case T_AlterEventTrigStmt: return "AlterEventTrigStmt"; break;
+		case T_RefreshMatViewStmt: return "RefreshMatViewStmt"; break;
+		case T_ReplicaIdentityStmt: return "ReplicaIdentityStmt"; break;
+		case T_AlterSystemStmt: return "AlterSystemStmt"; break;
+		case T_CreatePolicyStmt: return "CreatePolicyStmt"; break;
+		case T_AlterPolicyStmt: return "AlterPolicyStmt"; break;
+		case T_CreateTransformStmt: return "CreateTransformStmt"; break;
+		case T_CreateAmStmt: return "CreateAmStmt"; break;
+		case T_CreatePublicationStmt: return "CreatePublicationStmt"; break;
+		case T_AlterPublicationStmt: return "AlterPublicationStmt"; break;
+		case T_CreateSubscriptionStmt: return "CreateSubscriptionStmt"; break;
+		case T_AlterSubscriptionStmt: return "AlterSubscriptionStmt"; break;
+		case T_DropSubscriptionStmt: return "DropSubscriptionStmt"; break;
+		case T_CreateStatsStmt: return "CreateStatsStmt"; break;
+		case T_AlterCollationStmt: return "AlterCollationStmt"; break;
+		case T_CallStmt: return "CallStmt"; break;
+		case T_AlterStatsStmt: return "AlterStatsStmt"; break;
+		case T_A_Expr: return "A_Expr"; break;
+		case T_ColumnRef: return "ColumnRef"; break;
+		case T_ParamRef: return "ParamRef"; break;
+		case T_A_Const: return "A_Const"; break;
+		case T_FuncCall: return "FuncCall"; break;
+		case T_A_Star: return "A_Star"; break;
+		case T_A_Indices: return "A_Indices"; break;
+		case T_A_Indirection: return "A_Indirection"; break;
+		case T_A_ArrayExpr: return "A_ArrayExpr"; break;
+		case T_ResTarget: return "ResTarget"; break;
+		case T_MultiAssignRef: return "MultiAssignRef"; break;
+		case T_TypeCast: return "TypeCast"; break;
+		case T_CollateClause: return "CollateClause"; break;
+		case T_SortBy: return "SortBy"; break;
+		case T_WindowDef: return "WindowDef"; break;
+		case T_RangeSubselect: return "RangeSubselect"; break;
+		case T_RangeFunction: return "RangeFunction"; break;
+		case T_RangeTableSample: return "RangeTableSample"; break;
+		case T_RangeTableFunc: return "RangeTableFunc"; break;
+		case T_RangeTableFuncCol: return "RangeTableFuncCol"; break;
+		case T_TypeName: return "TypeName"; break;
+		case T_ColumnDef: return "ColumnDef"; break;
+		case T_IndexElem: return "IndexElem"; break;
+		case T_StatsElem: return "StatsElem"; break;
+		case T_Constraint: return "Constraint"; break;
+		case T_DefElem: return "DefElem"; break;
+		case T_RangeTblEntry: return "RangeTblEntry"; break;
+		case T_RangeTblFunction: return "RangeTblFunction"; break;
+		case T_TableSampleClause: return "TableSampleClause"; break;
+		case T_WithCheckOption: return "WithCheckOption"; break;
+		case T_SortGroupClause: return "SortGroupClause"; break;
+		case T_GroupingSet: return "GroupingSet"; break;
+		case T_WindowClause: return "WindowClause"; break;
+		case T_ObjectWithArgs: return "ObjectWithArgs"; break;
+		case T_AccessPriv: return "AccessPriv"; break;
+		case T_CreateOpClassItem: return "CreateOpClassItem"; break;
+		case T_TableLikeClause: return "TableLikeClause"; break;
+		case T_FunctionParameter: return "FunctionParameter"; break;
+		case T_LockingClause: return "LockingClause"; break;
+		case T_RowMarkClause: return "RowMarkClause"; break;
+		case T_XmlSerialize: return "XmlSerialize"; break;
+		case T_WithClause: return "WithClause"; break;
+		case T_InferClause: return "InferClause"; break;
+		case T_OnConflictClause: return "OnConflictClause"; break;
+		case T_CTESearchClause: return "CTESearchClause"; break;
+		case T_CTECycleClause: return "CTECycleClause"; break;
+		case T_CommonTableExpr: return "CommonTableExpr"; break;
+		case T_RoleSpec: return "RoleSpec"; break;
+		case T_TriggerTransition: return "TriggerTransition"; break;
+		case T_PartitionElem: return "PartitionElem"; break;
+		case T_PartitionSpec: return "PartitionSpec"; break;
+		case T_PartitionBoundSpec: return "PartitionBoundSpec"; break;
+		case T_PartitionRangeDatum: return "PartitionRangeDatum"; break;
+		case T_PartitionCmd: return "PartitionCmd"; break;
+		case T_VacuumRelation: return "VacuumRelation"; break;
+		case T_PublicationObjSpec: return "PublicationObjSpec"; break;
+		case T_PublicationTable: return "PublicationTable"; break;
+		case T_IdentifySystemCmd: return "IdentifySystemCmd"; break;
+		case T_BaseBackupCmd: return "BaseBackupCmd"; break;
+		case T_CreateReplicationSlotCmd: return "CreateReplicationSlotCmd"; break;
+		case T_DropReplicationSlotCmd: return "DropReplicationSlotCmd"; break;
+		case T_ReadReplicationSlotCmd: return "ReadReplicationSlotCmd"; break;
+		case T_StartReplicationCmd: return "StartReplicationCmd"; break;
+		case T_TimeLineHistoryCmd: return "TimeLineHistoryCmd"; break;
+		case T_TriggerData: return "TriggerData"; break;
+		case T_EventTriggerData: return "EventTriggerData"; break;
+		case T_ReturnSetInfo: return "ReturnSetInfo"; break;
+		case T_WindowObjectData: return "WindowObjectData"; break;
+		case T_TIDBitmap: return "TIDBitmap"; break;
+		case T_InlineCodeBlock: return "InlineCodeBlock"; break;
+		case T_FdwRoutine: return "FdwRoutine"; break;
+		case T_IndexAmRoutine: return "IndexAmRoutine"; break;
+		case T_TableAmRoutine: return "TableAmRoutine"; break;
+		case T_TsmRoutine: return "TsmRoutine"; break;
+		case T_ForeignKeyCacheInfo: return "ForeignKeyCacheInfo"; break;
+		case T_CallContext: return "CallContext"; break;
+		case T_SupportRequestSimplify: return "SupportRequestSimplify"; break;
+		case T_SupportRequestSelectivity: return "SupportRequestSelectivity"; break;
+		case T_SupportRequestCost: return "SupportRequestCost"; break;
+		case T_SupportRequestRows: return "SupportRequestRows"; break;
+		case T_SupportRequestIndexCondition: return "SupportRequestIndexCondition"; break;
+		default:
+			break;
+	}
+	return "UNRECOGNIZED NodeTag";
+}
+
+static char *
+accesstype_to_string(ObjectAccessType access, int subId)
+{
+	const char *type;
+
+	switch (access)
+	{
+		case OAT_POST_CREATE:
+			type = "create";
+			break;
+		case OAT_DROP:
+			type = "drop";
+			break;
+		case OAT_POST_ALTER:
+			type = "alter";
+			break;
+		case OAT_NAMESPACE_SEARCH:
+			type = "namespace search";
+			break;
+		case OAT_FUNCTION_EXECUTE:
+			type = "execute";
+			break;
+		case OAT_TRUNCATE:
+			type = "truncate";
+			break;
+		default:
+			type = "UNRECOGNIZED ObjectAccessType";
+	}
+
+	if (subId & ACL_SET_VALUE)
+		return psprintf("%s (set)", type);
+	if (subId & ACL_ALTER_SYSTEM)
+		return psprintf("%s (alter system set)", type);
+
+	return  psprintf("%s (subId=%d)", type, subId);
+}
+
+static char *
+accesstype_arg_to_string(ObjectAccessType access, void *arg)
+{
+	if (arg == NULL)
+		return pstrdup("extra info null");
+
+	switch (access)
+	{
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *)arg;
+				return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
+			}
+			break;
+		case OAT_DROP:
+			{
+				ObjectAccessDrop *drop_arg = (ObjectAccessDrop *)arg;
+
+				return psprintf("%s%s%s%s%s%s",
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "internal action," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "concurrent drop," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "suppress notices," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "keep original object," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "keep extensions," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "normal concurrent drop," : ""));
+			}
+			break;
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter*)arg;
+
+				return psprintf("%s %s auxiliary object",
+					(pa_arg->is_internal ? "internal" : "explicit"),
+					(OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
+			}
+			break;
+		case OAT_NAMESPACE_SEARCH:
+			{
+				ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *)arg;
+
+				return psprintf("%s, %s",
+					(ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
+					(ns_arg->result ? "allowed" : "denied"));
+			}
+			break;
+		case OAT_TRUNCATE:
+		case OAT_FUNCTION_EXECUTE:
+			/* hook takes no arg. */
+			return pstrdup("unexpected extra info pointer received");
+		default:
+			return pstrdup("cannot parse extra info for unrecognized access type");
+	}
+
+	return pstrdup("unknown");
+}
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.conf b/src/test/modules/test_oat_hooks/test_oat_hooks.conf
new file mode 100644
index 0000000000..a44cbdd4a4
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.conf
@@ -0,0 +1 @@
+shared_preload_libraries = test_oat_hooks
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.control b/src/test/modules/test_oat_hooks/test_oat_hooks.control
new file mode 100644
index 0000000000..3d3cf4a8ac
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.control
@@ -0,0 +1,4 @@
+comment = 'Test code for Object Access hooks'
+default_version = '1.0'
+module_pathname = '$libdir/test_oat_hooks'
+relocatable = true
-- 
2.35.1

#7Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Andrew Dunstan (#5)
Re: New Object Access Type hooks

On Mar 21, 2022, at 8:41 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

My first inclination is to say it's probably ok. The immediately obvious
alternative would be to create yet another set of functions that don't
have classId parameters. That doesn't seem attractive.

Modulo that issue I think patch 1 is basically ok, but we should fix the
comments in objectaccess.c. Rather than "It is [the] entrypoint ..." we
should have something like "Oid variant entrypoint ..." and "Name
variant entrypoint ...", and also fix the function names in the comments.

Joshua,

Do you care to create a new version of this, perhaps based on the v2-0001 patch I just posted?


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#8Andrew Dunstan
andrew@dunslane.net
In reply to: Mark Dilger (#6)
Re: New Object Access Type hooks

On 3/21/22 15:57, Mark Dilger wrote:

On Mar 18, 2022, at 3:04 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

I haven't looked at it in detail, but regarding the test code I'm not
sure why there's a .control file, since this isn't a loadable extension,
not why there's a test_oat_hooks.h file.

The .control file exists because the test defines a loadable module which defines the hooks.

To the best of my knowledge .control files are only used by extensions,
not by other modules. They are only referenced in
src/backend/commands/extension.c in the backend code. For example,
auto_explain which is a loadable module but not en extension does not
have one, and I bet if you remove it you'll find this will work just fine.

cheers

andrew

--

Andrew Dunstan
EDB: https://www.enterprisedb.com

#9Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Andrew Dunstan (#8)
2 attachment(s)
Re: New Object Access Type hooks

On Mar 21, 2022, at 1:30 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

To the best of my knowledge .control files are only used by extensions,
not by other modules. They are only referenced in
src/backend/commands/extension.c in the backend code. For example,
auto_explain which is a loadable module but not en extension does not
have one, and I bet if you remove it you'll find this will work just fine.

Fixed, also with adjustments to Joshua's function comments.

Attachments:

v3-0001-Add-String-object-access-hooks.patchapplication/octet-stream; name=v3-0001-Add-String-object-access-hooks.patch; x-unix-mode=0644Download
From beec520acacf2df3f19e71bdf511e27f1f3143aa Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Thu, 17 Mar 2022 19:30:09 -0700
Subject: [PATCH v3 1/2] Add String object access hooks

The first user of these will be the GUC access controls

Written by Joshua Brindle; refactored by Mark Dilger
---
 src/backend/catalog/objectaccess.c | 140 +++++++++++++++++++++++++++--
 src/backend/utils/misc/guc.c       |  17 ++++
 src/include/catalog/objectaccess.h |  70 ++++++++++++++-
 src/include/nodes/parsenodes.h     |   4 +-
 src/include/utils/acl.h            |   4 +-
 5 files changed, 226 insertions(+), 9 deletions(-)

diff --git a/src/backend/catalog/objectaccess.c b/src/backend/catalog/objectaccess.c
index 549fac4539..38922294e2 100644
--- a/src/backend/catalog/objectaccess.c
+++ b/src/backend/catalog/objectaccess.c
@@ -20,11 +20,13 @@
  * and logging plugins.
  */
 object_access_hook_type object_access_hook = NULL;
+object_access_hook_type_str object_access_hook_str = NULL;
+
 
 /*
  * RunObjectPostCreateHook
  *
- * It is entrypoint of OAT_POST_CREATE event
+ * OAT_POST_CREATE object ID based event hook entrypoint
  */
 void
 RunObjectPostCreateHook(Oid classId, Oid objectId, int subId,
@@ -46,7 +48,7 @@ RunObjectPostCreateHook(Oid classId, Oid objectId, int subId,
 /*
  * RunObjectDropHook
  *
- * It is entrypoint of OAT_DROP event
+ * OAT_DROP object ID based event hook entrypoint
  */
 void
 RunObjectDropHook(Oid classId, Oid objectId, int subId,
@@ -68,7 +70,7 @@ RunObjectDropHook(Oid classId, Oid objectId, int subId,
 /*
  * RunObjectTruncateHook
  *
- * It is the entrypoint of OAT_TRUNCATE event
+ * OAT_TRUNCATE object ID based event hook entrypoint
  */
 void
 RunObjectTruncateHook(Oid objectId)
@@ -84,7 +86,7 @@ RunObjectTruncateHook(Oid objectId)
 /*
  * RunObjectPostAlterHook
  *
- * It is entrypoint of OAT_POST_ALTER event
+ * OAT_POST_ALTER object ID based event hook entrypoint
  */
 void
 RunObjectPostAlterHook(Oid classId, Oid objectId, int subId,
@@ -107,7 +109,7 @@ RunObjectPostAlterHook(Oid classId, Oid objectId, int subId,
 /*
  * RunNamespaceSearchHook
  *
- * It is entrypoint of OAT_NAMESPACE_SEARCH event
+ * OAT_NAMESPACE_SEARCH object ID based event hook entrypoint
  */
 bool
 RunNamespaceSearchHook(Oid objectId, bool ereport_on_violation)
@@ -131,7 +133,7 @@ RunNamespaceSearchHook(Oid objectId, bool ereport_on_violation)
 /*
  * RunFunctionExecuteHook
  *
- * It is entrypoint of OAT_FUNCTION_EXECUTE event
+ * OAT_FUNCTION_EXECUTE object ID based event hook entrypoint
  */
 void
 RunFunctionExecuteHook(Oid objectId)
@@ -143,3 +145,129 @@ RunFunctionExecuteHook(Oid objectId)
 						   ProcedureRelationId, objectId, 0,
 						   NULL);
 }
+
+/* String versions */
+
+
+/*
+ * RunObjectPostCreateHookStr
+ *
+ * OAT_POST_CREATE object name based event hook entrypoint
+ */
+void
+RunObjectPostCreateHookStr(Oid classId, const char *objectName, int subId,
+						bool is_internal)
+{
+	ObjectAccessPostCreate pc_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&pc_arg, 0, sizeof(ObjectAccessPostCreate));
+	pc_arg.is_internal = is_internal;
+
+	(*object_access_hook_str) (OAT_POST_CREATE,
+						   classId, objectName, subId,
+						   (void *) &pc_arg);
+}
+
+/*
+ * RunObjectDropHookStr
+ *
+ * OAT_DROP object name based event hook entrypoint
+ */
+void
+RunObjectDropHookStr(Oid classId, const char *objectName, int subId,
+				  int dropflags)
+{
+	ObjectAccessDrop drop_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&drop_arg, 0, sizeof(ObjectAccessDrop));
+	drop_arg.dropflags = dropflags;
+
+	(*object_access_hook_str) (OAT_DROP,
+						   classId, objectName, subId,
+						   (void *) &drop_arg);
+}
+
+/*
+ * RunObjectTruncateHookStr
+ *
+ * OAT_TRUNCATE object name based event hook entrypoint
+ */
+void
+RunObjectTruncateHookStr(const char *objectName)
+{
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	(*object_access_hook_str) (OAT_TRUNCATE,
+						   RelationRelationId, objectName, 0,
+						   NULL);
+}
+
+/*
+ * RunObjectPostAlterHookStr
+ *
+ * OAT_POST_ALTER object name based event hook entrypoint
+ */
+void
+RunObjectPostAlterHookStr(Oid classId, const char *objectName, int subId,
+					   Oid auxiliaryId, bool is_internal)
+{
+	ObjectAccessPostAlter pa_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&pa_arg, 0, sizeof(ObjectAccessPostAlter));
+	pa_arg.auxiliary_id = auxiliaryId;
+	pa_arg.is_internal = is_internal;
+
+	(*object_access_hook_str) (OAT_POST_ALTER,
+						   classId, objectName, subId,
+						   (void *) &pa_arg);
+}
+
+/*
+ * RunNamespaceSearchHookStr
+ *
+ * OAT_NAMESPACE_SEARCH object name based event hook entrypoint
+ */
+bool
+RunNamespaceSearchHookStr(const char *objectName, bool ereport_on_violation)
+{
+	ObjectAccessNamespaceSearch ns_arg;
+
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	memset(&ns_arg, 0, sizeof(ObjectAccessNamespaceSearch));
+	ns_arg.ereport_on_violation = ereport_on_violation;
+	ns_arg.result = true;
+
+	(*object_access_hook_str) (OAT_NAMESPACE_SEARCH,
+						   NamespaceRelationId, objectName, 0,
+						   (void *) &ns_arg);
+
+	return ns_arg.result;
+}
+
+/*
+ * RunFunctionExecuteHookStr
+ *
+ * OAT_FUNCTION_EXECUTE object name based event hook entrypoint
+ */
+void
+RunFunctionExecuteHookStr(const char *objectName)
+{
+	/* caller should check, but just in case... */
+	Assert(object_access_hook_str != NULL);
+
+	(*object_access_hook_str) (OAT_FUNCTION_EXECUTE,
+						   ProcedureRelationId, objectName, 0,
+						   NULL);
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e7f0a380e6..932aefc777 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -43,6 +43,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
@@ -8736,6 +8737,18 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 		replace_auto_config_value(&head, &tail, name, value);
 	}
 
+	/*
+	 * Invoke the post-alter hook for altering this GUC variable.
+	 *
+	 * We do this here rather than at the end, because ALTER SYSTEM is not
+	 * transactional.  If the hook aborts our transaction, it will be cleaner
+	 * to do so before we touch any files.
+	 */
+	InvokeObjectPostAlterHookArgStr(InvalidOid, name,
+									ACL_ALTER_SYSTEM,
+									altersysstmt->setstmt->kind,
+									false);
+
 	/*
 	 * To ensure crash safety, first write the new file data to a temp file,
 	 * then atomically rename it into place.
@@ -8907,6 +8920,10 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 			ResetAllOptions();
 			break;
 	}
+
+	/* Invoke the post-alter hook for setting this GUC variable. */
+	InvokeObjectPostAlterHookArgStr(InvalidOid, stmt->name,
+									ACL_SET_VALUE, stmt->kind, false);
 }
 
 /*
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index 508dfd0a6b..4d54ae2a7d 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -121,15 +121,23 @@ typedef struct
 	bool		result;
 } ObjectAccessNamespaceSearch;
 
-/* Plugin provides a hook function matching this signature. */
+/* Plugin provides a hook function matching one or both of these signatures. */
 typedef void (*object_access_hook_type) (ObjectAccessType access,
 										 Oid classId,
 										 Oid objectId,
 										 int subId,
 										 void *arg);
 
+typedef void (*object_access_hook_type_str) (ObjectAccessType access,
+										 Oid classId,
+										 const char *objectStr,
+										 int subId,
+										 void *arg);
+
 /* Plugin sets this variable to a suitable hook function. */
 extern PGDLLIMPORT object_access_hook_type object_access_hook;
+extern PGDLLIMPORT object_access_hook_type_str object_access_hook_str;
+
 
 /* Core code uses these functions to call the hook (see macros below). */
 extern void RunObjectPostCreateHook(Oid classId, Oid objectId, int subId,
@@ -142,6 +150,18 @@ extern void RunObjectPostAlterHook(Oid classId, Oid objectId, int subId,
 extern bool RunNamespaceSearchHook(Oid objectId, bool ereport_on_violation);
 extern void RunFunctionExecuteHook(Oid objectId);
 
+/* String versions */
+extern void RunObjectPostCreateHookStr(Oid classId, const char *objectStr, int subId,
+									bool is_internal);
+extern void RunObjectDropHookStr(Oid classId, const char *objectStr, int subId,
+							  int dropflags);
+extern void RunObjectTruncateHookStr(const char *objectStr);
+extern void RunObjectPostAlterHookStr(Oid classId, const char *objectStr, int subId,
+								   Oid auxiliaryId, bool is_internal);
+extern bool RunNamespaceSearchHookStr(const char *objectStr, bool ereport_on_violation);
+extern void RunFunctionExecuteHookStr(const char *objectStr);
+
+
 /*
  * The following macros are wrappers around the functions above; these should
  * normally be used to invoke the hook in lieu of calling the above functions
@@ -194,4 +214,52 @@ extern void RunFunctionExecuteHook(Oid objectId);
 			RunFunctionExecuteHook(objectId);	\
 	} while(0)
 
+
+#define InvokeObjectPostCreateHookStr(classId,objectName,subId)			\
+	InvokeObjectPostCreateHookArgStr((classId),(objectName),(subId),false)
+#define InvokeObjectPostCreateHookArgStr(classId,objectName,subId,is_internal) \
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectPostCreateHookStr((classId),(objectName),(subId),	\
+									(is_internal));					\
+	} while(0)
+
+#define InvokeObjectDropHookStr(classId,objectName,subId)				\
+	InvokeObjectDropHookArgStr((classId),(objectName),(subId),0)
+#define InvokeObjectDropHookArgStr(classId,objectName,subId,dropflags)	\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectDropHookStr((classId),(objectName),(subId),			\
+							  (dropflags));							\
+	} while(0)
+
+#define InvokeObjectTruncateHookStr(objectName)							\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectTruncateHookStr(objectName);						\
+	} while(0)
+
+#define InvokeObjectPostAlterHookStr(className,objectName,subId)			\
+	InvokeObjectPostAlterHookArgStr((classId),(objectName),(subId),		\
+								 InvalidOid,false)
+#define InvokeObjectPostAlterHookArgStr(classId,objectName,subId,		\
+									 auxiliaryId,is_internal)		\
+	do {															\
+		if (object_access_hook_str)										\
+			RunObjectPostAlterHookStr((classId),(objectName),(subId),	\
+								   (auxiliaryId),(is_internal));	\
+	} while(0)
+
+#define InvokeNamespaceSearchHookStr(objectName, ereport_on_violation)	\
+	(!object_access_hook_str										\
+	 ? true															\
+	 : RunNamespaceSearchHookStr((objectName), (ereport_on_violation)))
+
+#define InvokeFunctionExecuteHookStr(objectName)		\
+	do {										\
+		if (object_access_hook_str)					\
+			RunFunctionExecuteHookStr(objectName);	\
+	} while(0)
+
+
 #endif							/* OBJECTACCESS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1617702d9d..dc361d62e2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -92,7 +92,9 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_SET_VALUE	(1<<12) /* for configuration parameters */
+#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
+#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 1ce4c5556e..91ce3d8e9c 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -146,9 +146,11 @@ typedef struct ArrayType Acl;
 #define ACL_CREATE_CHR			'C'
 #define ACL_CREATE_TEMP_CHR		'T'
 #define ACL_CONNECT_CHR			'c'
+#define ACL_SET_VALUE_CHR		's'
+#define ACL_ALTER_SYSTEM_CHR	'A'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcsA"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
-- 
2.35.1

v3-0002-Add-regression-tests-of-Object-Access-Type-hooks.patchapplication/octet-stream; name=v3-0002-Add-regression-tests-of-Object-Access-Type-hooks.patch; x-unix-mode=0644Download
From f76a2f898b22ac3e9dc0bab61c1be44366780b42 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Thu, 17 Mar 2022 15:05:18 -0700
Subject: [PATCH v3 2/2] Add regression tests of Object Access Type hooks

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/test_oat_hooks/.gitignore    |   4 +
 src/test/modules/test_oat_hooks/Makefile      |  24 +
 src/test/modules/test_oat_hooks/README        |  86 ++
 .../expected/test_oat_hooks.out               | 213 ++++
 .../test_oat_hooks/sql/test_oat_hooks.sql     |  59 ++
 .../modules/test_oat_hooks/test_oat_hooks.c   | 934 ++++++++++++++++++
 .../test_oat_hooks/test_oat_hooks.conf        |   1 +
 8 files changed, 1322 insertions(+)
 create mode 100644 src/test/modules/test_oat_hooks/.gitignore
 create mode 100644 src/test/modules/test_oat_hooks/Makefile
 create mode 100644 src/test/modules/test_oat_hooks/README
 create mode 100644 src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
 create mode 100644 src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
 create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.c
 create mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.conf

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index dffc79b2d9..9090226daa 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -20,6 +20,7 @@ SUBDIRS = \
 		  test_ginpostinglist \
 		  test_integerset \
 		  test_misc \
+		  test_oat_hooks \
 		  test_parser \
 		  test_pg_dump \
 		  test_predtest \
diff --git a/src/test/modules/test_oat_hooks/.gitignore b/src/test/modules/test_oat_hooks/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_oat_hooks/Makefile b/src/test/modules/test_oat_hooks/Makefile
new file mode 100644
index 0000000000..d0df9c1abb
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/Makefile
@@ -0,0 +1,24 @@
+# src/test/modules/test_oat_hooks/Makefile
+
+MODULE_big = test_oat_hooks
+OBJS = \
+	$(WIN32RES) \
+	test_oat_hooks.o
+PGFILEDESC = "test_oat_hooks - example use of object access hooks"
+
+REGRESS = test_oat_hooks
+REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_oat_hooks/test_oat_hooks.conf
+# Disabled because these tests require "shared_preload_libraries=test_oat_hooks",
+# which typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_oat_hooks
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_oat_hooks/README b/src/test/modules/test_oat_hooks/README
new file mode 100644
index 0000000000..a7c400c747
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/README
@@ -0,0 +1,86 @@
+OVERVIEW
+========
+
+This test module, "test_oat_hooks", is an example of how to use the object
+access hooks (OAT) to enforce mandatory access controls (MAC).
+
+The testing strategy is as follows:  When this module loads, it registers hooks
+of various types.  (See below.)  GUCs are defined to control each hook,
+determining whether the hook allows or denies actions for which it fires.  A
+single additional GUC controls the verbosity of the hooks.  GUCs default to
+permissive/quiet, which allows the module to load without generating noise in
+the log or denying any activity in the run-up to the regression test beginning.
+When the test begins, it uses SET commands to turn on logging and to control
+each hook's permissive/restrictive behavior.  Various SQL statements are run
+under both superuser and ordinary user permissions.  The output is compared
+against the expected output to verify that the hooks behaved and fired in the
+order by expect.
+
+Because users may care about the firing order of other system hooks relative to
+OAT hooks, ProcessUtility hooks and ExecutorCheckPerms hooks are also
+registered by this module, with their own logging and allow/deny behavior.
+
+
+SUSET test configuration GUCs
+=============================
+
+The following configuration parameters (GUCs) control this test module's Object
+Access Type (OAT), Process Utility and Executor Check Permissions hooks.  The
+general pattern is that each hook has a corresponding GUC which controls
+whether the hook will allow or deny operations for which the hook gets called.
+A real-world OAT hook should certainly provide more fine-grained conrol than
+merely "allow-all" vs. "deny-all", but for testing this is sufficient.
+
+Note that even when these hooks allow an action, the core permissions system
+may still refuse the action.  The firing order of the hooks relative to the
+core permissions system can be inferred from which NOTICE messages get emitted
+before an action is refused.
+
+Each hook applies the allow vs. deny setting to all operations performed by
+non-superusers.
+
+- test_oat_hooks.deny_set_variable
+
+  Controls whether the object_access_hook_str MAC function rejects attempts to
+  set a configuration parameter.
+
+- test_oat_hooks.deny_alter_system
+
+  Controls whether the object_access_hook_str MAC function rejects attempts to
+  alter system set a configuration parameter.
+
+- test_oat_hooks.deny_object_access
+
+  Controls whether the object_access_hook MAC function rejects all operations
+  for which it is called.
+
+- test_oat_hooks.deny_exec_perms
+
+  Controls whether the exec_check_perms MAC function rejects all operations for
+  which it is called.
+
+- test_oat_hooks.deny_utility_commands
+
+  Controls whether the ProcessUtility_hook function rejects all operations for
+  which it is called.
+
+- test_oat_hooks.audit
+
+  Controls whether each hook logs NOTICE messages for each attempt, along with
+  success or failure status.  Note that clearing or setting this GUC may itself
+  generate NOTICE messages appearing before but not after, or after but not
+  before, the new setting takes effect.
+
+
+Functions
+=========
+
+The module registers hooks by the following names:
+
+- REGRESS_object_access_hook
+
+- REGRESS_object_access_hook_str
+
+- REGRESS_exec_check_perms
+
+- REGRESS_utility_command
diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
new file mode 100644
index 0000000000..b0e3fe6e68
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
@@ -0,0 +1,213 @@
+LOAD 'test_oat_hooks';
+-- Turn on logging messages to see audit messages
+SET client_min_messages = 'LOG';
+-- SET commands fire both the ProcessUtility_hook and the
+-- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
+-- initial "attempting" audit message from the ProcessUtility_hook, but we
+-- should thereafter see the audit messages
+SET test_oat_hooks.audit = true;
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.audit]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.audit]
+NOTICE:  in process utility: superuser finished set
+-- Create objects for use in the test
+CREATE USER regress_test_user;
+NOTICE:  in process utility: superuser attempting CreateRoleStmt
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in process utility: superuser finished CreateRoleStmt
+CREATE TABLE regress_test_table (t text);
+NOTICE:  in process utility: superuser attempting CreateStmt
+NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: CREATE TABLE regress_test_table (t text);
+                     ^
+NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: CREATE TABLE regress_test_table (t text);
+                     ^
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0) [internal]
+NOTICE:  in object access: superuser attempting create (subId=0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0) [internal]
+NOTICE:  in process utility: superuser finished CreateStmt
+GRANT SELECT ON Table regress_test_table TO public;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
+	SELECT $1;
+$$ LANGUAGE sql;
+NOTICE:  in process utility: superuser attempting CreateFunctionStmt
+NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in process utility: superuser finished CreateFunctionStmt
+GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Do a few things as superuser
+SELECT * FROM regress_test_table;
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+-- Do those same things as non-superuser
+SET SESSION AUTHORIZATION regress_test_user;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: non-superuser finished set
+SELECT * FROM regress_test_table;
+NOTICE:  in object access: non-superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in object access: non-superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in executor check perms: non-superuser attempting execute
+NOTICE:  in executor check perms: non-superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: non-superuser attempting execute
+NOTICE:  in executor check perms: non-superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: non-superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: non-superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  must be superuser to execute ALTER SYSTEM command
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  must be superuser to execute ALTER SYSTEM command
+RESET SESSION AUTHORIZATION;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: superuser finished set
+-- Turn off non-superuser permissions
+SET test_oat_hooks.deny_set_variable = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_alter_system = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_object_access = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_object_access]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_object_access]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_exec_perms = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.deny_utility_commands = true;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_utility_commands]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_utility_commands]
+NOTICE:  in process utility: superuser finished set
+-- Try again as non-superuser with permisisons denied
+SET SESSION AUTHORIZATION regress_test_user;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
+ERROR:  permission denied: set session_authorization
+SELECT * FROM regress_test_table;
+NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+LINE 1: SELECT * FROM regress_test_table;
+                      ^
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ t 
+---
+(0 rows)
+
+SELECT regress_test_func('arg');
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
+ regress_test_func 
+-------------------
+ arg
+(1 row)
+
+SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+RESET work_mem;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in process utility: superuser finished set
+ALTER SYSTEM SET work_mem = 8192;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+ALTER SYSTEM RESET work_mem;
+NOTICE:  in process utility: superuser attempting alter system
+NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in process utility: superuser finished alter system
+RESET SESSION AUTHORIZATION;
+NOTICE:  in process utility: superuser attempting set
+NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in process utility: superuser finished set
+SET test_oat_hooks.audit = false;
+NOTICE:  in process utility: superuser attempting set
+RESET client_min_messages;
diff --git a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
new file mode 100644
index 0000000000..86addbd670
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
@@ -0,0 +1,59 @@
+LOAD 'test_oat_hooks';
+
+-- Turn on logging messages to see audit messages
+SET client_min_messages = 'LOG';
+
+-- SET commands fire both the ProcessUtility_hook and the
+-- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
+-- initial "attempting" audit message from the ProcessUtility_hook, but we
+-- should thereafter see the audit messages
+SET test_oat_hooks.audit = true;
+
+-- Create objects for use in the test
+CREATE USER regress_test_user;
+CREATE TABLE regress_test_table (t text);
+GRANT SELECT ON Table regress_test_table TO public;
+CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
+	SELECT $1;
+$$ LANGUAGE sql;
+GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public;
+
+-- Do a few things as superuser
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+
+-- Do those same things as non-superuser
+SET SESSION AUTHORIZATION regress_test_user;
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+RESET SESSION AUTHORIZATION;
+
+-- Turn off non-superuser permissions
+SET test_oat_hooks.deny_set_variable = true;
+SET test_oat_hooks.deny_alter_system = true;
+SET test_oat_hooks.deny_object_access = true;
+SET test_oat_hooks.deny_exec_perms = true;
+SET test_oat_hooks.deny_utility_commands = true;
+
+-- Try again as non-superuser with permisisons denied
+SET SESSION AUTHORIZATION regress_test_user;
+SELECT * FROM regress_test_table;
+SELECT regress_test_func('arg');
+SET work_mem = 8192;
+RESET work_mem;
+ALTER SYSTEM SET work_mem = 8192;
+ALTER SYSTEM RESET work_mem;
+
+RESET SESSION AUTHORIZATION;
+
+SET test_oat_hooks.audit = false;
+
+RESET client_min_messages;
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
new file mode 100644
index 0000000000..b1709f4d63
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -0,0 +1,934 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_oat_hooks.c
+ *		Code for testing mandatory access control (MAC) using object access hooks.
+ *
+ * Copyright (c) 2015-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_oat_hooks/test_oat_hooks.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/dependency.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "executor/executor.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "tcop/utility.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * GUCs controlling which operations to deny
+ */
+static bool REGRESS_deny_set_variable = false;
+static bool REGRESS_deny_alter_system = false;
+static bool REGRESS_deny_object_access = false;
+static bool REGRESS_deny_exec_perms = false;
+static bool REGRESS_deny_utility_commands = false;
+static bool REGRESS_audit = false;
+
+/* Saved hook values in case of unload */
+static object_access_hook_type next_object_access_hook = NULL;
+static object_access_hook_type_str next_object_access_hook_str = NULL;
+static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+
+/* Test Object Access Type Hook hooks */
+static void REGRESS_object_access_hook_str(ObjectAccessType access,
+										   Oid classId, const char *objName,
+										   int subId, void *arg);
+static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
+									   Oid objectId, int subId, void *arg);
+static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static void REGRESS_utility_command(PlannedStmt *pstmt,
+									const char *queryString, bool readOnlyTree,
+									ProcessUtilityContext context,
+									ParamListInfo params,
+									QueryEnvironment *queryEnv,
+									DestReceiver *dest, QueryCompletion *qc);
+
+/* Helper functions */
+static const char *nodetag_to_string(NodeTag tag);
+static char *accesstype_to_string(ObjectAccessType access, int subId);
+static char *accesstype_arg_to_string(ObjectAccessType access, void *arg);
+
+
+void		_PG_init(void);
+void		_PG_fini(void);
+
+/*
+ * Module load/unload callback
+ */
+void
+_PG_init(void)
+{
+	/*
+	 * We allow to load the Object Access Type test module on single-user-mode
+	 * or shared_preload_libraries settings only.
+	 */
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("test_oat_hooks must be loaded via shared_preload_libraries")));
+
+	/*
+	 * test_oat_hooks.deny_set_variable = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_set_variable",
+							 "Deny non-superuser set permissions",
+							 NULL,
+							 &REGRESS_deny_set_variable,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_alter_system = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_alter_system",
+							 "Deny non-superuser alter system set permissions",
+							 NULL,
+							 &REGRESS_deny_alter_system,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_object_access = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_object_access",
+							 "Deny non-superuser object access permissions",
+							 NULL,
+							 &REGRESS_deny_object_access,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_exec_perms = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms",
+							 "Deny non-superuser exec permissions",
+							 NULL,
+							 &REGRESS_deny_exec_perms,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.deny_utility_commands = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands",
+							 "Deny non-superuser utility commands",
+							 NULL,
+							 &REGRESS_deny_utility_commands,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	/*
+	 * test_oat_hooks.audit = (on|off)
+	 */
+	DefineCustomBoolVariable("test_oat_hooks.audit",
+							 "Turn on/off debug audit messages",
+							 NULL,
+							 &REGRESS_audit,
+							 false,
+							 PGC_SUSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	MarkGUCPrefixReserved("test_oat_hooks");
+
+	/* Object access hook */
+	next_object_access_hook = object_access_hook;
+	object_access_hook = REGRESS_object_access_hook;
+
+	/* Object access hook str */
+	next_object_access_hook_str = object_access_hook_str;
+	object_access_hook_str = REGRESS_object_access_hook_str;
+
+	/* DML permission check */
+	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+
+	/* ProcessUtility hook */
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = REGRESS_utility_command;
+}
+
+void
+_PG_fini(void)
+{
+	/* Unload hooks */
+	if (object_access_hook == REGRESS_object_access_hook)
+		object_access_hook = next_object_access_hook;
+
+	if (object_access_hook_str == REGRESS_object_access_hook_str)
+		object_access_hook_str = next_object_access_hook_str;
+
+	if (ExecutorCheckPerms_hook == REGRESS_exec_check_perms)
+		ExecutorCheckPerms_hook = next_exec_check_perms_hook;
+
+	if (ProcessUtility_hook == REGRESS_utility_command)
+		ProcessUtility_hook = next_ProcessUtility_hook;
+}
+
+static void
+emit_audit_message(const char *type, const char *hook, char *action, char *objName)
+{
+	if (REGRESS_audit)
+	{
+		const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
+
+		if (objName)
+			ereport(NOTICE,
+					(errcode(ERRCODE_INTERNAL_ERROR),
+					 errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
+		else
+			ereport(NOTICE,
+					(errcode(ERRCODE_INTERNAL_ERROR),
+					 errmsg("in %s: %s %s %s", hook, who, type, action)));
+	}
+
+	if (action)
+		pfree(action);
+	if (objName)
+		pfree(objName);
+}
+
+static void
+audit_attempt(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("attempting", hook, action, objName);
+}
+
+static void
+audit_success(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("finished", hook, action, objName);
+}
+
+static void
+audit_failure(const char *hook, char *action, char *objName)
+{
+	emit_audit_message("denied", hook, action, objName);
+}
+
+static void
+REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
+{
+	audit_attempt("object_access_hook_str",
+				  accesstype_to_string(access, subId),
+				  pstrdup(objName));
+
+	if (next_object_access_hook_str)
+	{
+		(*next_object_access_hook_str)(access, classId, objName, subId, arg);
+	}
+
+	switch (access)
+	{
+		case OAT_POST_ALTER:
+			if (subId & ACL_SET_VALUE)
+			{
+				if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("permission denied: set %s", objName)));
+			}
+			else if (subId & ACL_ALTER_SYSTEM)
+			{
+				if (REGRESS_deny_alter_system && !superuser_arg(GetUserId()))
+					ereport(ERROR,
+							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+							 errmsg("permission denied: alter system set %s", objName)));
+			}
+			else
+				elog(ERROR, "Unknown SettingAclRelationId subId: %d", subId);
+			break;
+		default:
+			break;
+	}
+
+	audit_success("object_access_hook_str",
+				  accesstype_to_string(access, subId),
+				  pstrdup(objName));
+}
+
+static void
+REGRESS_object_access_hook (ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
+{
+	audit_attempt("object access",
+				  accesstype_to_string(access, 0),
+				  accesstype_arg_to_string(access, arg));
+
+	if (REGRESS_deny_object_access && !superuser_arg(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s [%s]",
+						accesstype_to_string(access, 0),
+						accesstype_arg_to_string(access, arg))));
+
+	/* Forward to next hook in the chain */
+	if (next_object_access_hook)
+		(*next_object_access_hook)(access, classId, objectId, subId, arg);
+
+	audit_success("object access",
+				  accesstype_to_string(access, 0),
+				  accesstype_arg_to_string(access, arg));
+}
+
+static bool
+REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+{
+	bool		am_super = superuser_arg(GetUserId());
+	bool		allow = true;
+
+	audit_attempt("executor check perms", pstrdup("execute"), NULL);
+
+	/* Perform our check */
+	allow = !REGRESS_deny_exec_perms || am_super;
+	if (do_abort && !allow)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s", "execute")));
+
+	/* Forward to next hook in the chain */
+	if (next_exec_check_perms_hook &&
+		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		allow = false;
+
+	if (allow)
+		audit_success("executor check perms",
+					  pstrdup("execute"),
+					  NULL);
+	else
+		audit_failure("executor check perms",
+					  pstrdup("execute"),
+					  NULL);
+
+	return allow;
+}
+
+static void
+REGRESS_utility_command(PlannedStmt *pstmt,
+					  const char *queryString,
+					  bool readOnlyTree,
+					  ProcessUtilityContext context,
+					  ParamListInfo params,
+					  QueryEnvironment *queryEnv,
+					  DestReceiver *dest,
+					  QueryCompletion *qc)
+{
+	Node	   *parsetree = pstmt->utilityStmt;
+
+	const char *action;
+	NodeTag tag = nodeTag(parsetree);
+
+	switch (tag)
+	{
+		case T_VariableSetStmt:
+			action = "set";
+			break;
+		case T_AlterSystemStmt:
+			action = "alter system";
+			break;
+		case T_LoadStmt:
+			action = "load";
+			break;
+		default:
+			action = nodetag_to_string(tag);
+			break;
+	}
+
+	audit_attempt("process utility",
+				  pstrdup(action),
+				  NULL);
+
+	/* Check permissions */
+	if (REGRESS_deny_utility_commands && !superuser_arg(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: %s", action)));
+
+	/* Forward to next hook in the chain */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
+									 context, params, queryEnv,
+									 dest, qc);
+	else
+		standard_ProcessUtility(pstmt, queryString, readOnlyTree,
+								context, params, queryEnv,
+								dest, qc);
+
+	/* We're done */
+	audit_success("process utility",
+				  pstrdup(action),
+				  NULL);
+}
+
+static const char *
+nodetag_to_string(NodeTag tag)
+{
+	switch (tag)
+	{
+		case T_Invalid: return "Invalid"; break;
+		case T_IndexInfo: return "IndexInfo"; break;
+		case T_ExprContext: return "ExprContext"; break;
+		case T_ProjectionInfo: return "ProjectionInfo"; break;
+		case T_JunkFilter: return "JunkFilter"; break;
+		case T_OnConflictSetState: return "OnConflictSetState"; break;
+		case T_ResultRelInfo: return "ResultRelInfo"; break;
+		case T_EState: return "EState"; break;
+		case T_TupleTableSlot: return "TupleTableSlot"; break;
+		case T_Plan: return "Plan"; break;
+		case T_Result: return "Result"; break;
+		case T_ProjectSet: return "ProjectSet"; break;
+		case T_ModifyTable: return "ModifyTable"; break;
+		case T_Append: return "Append"; break;
+		case T_MergeAppend: return "MergeAppend"; break;
+		case T_RecursiveUnion: return "RecursiveUnion"; break;
+		case T_BitmapAnd: return "BitmapAnd"; break;
+		case T_BitmapOr: return "BitmapOr"; break;
+		case T_Scan: return "Scan"; break;
+		case T_SeqScan: return "SeqScan"; break;
+		case T_SampleScan: return "SampleScan"; break;
+		case T_IndexScan: return "IndexScan"; break;
+		case T_IndexOnlyScan: return "IndexOnlyScan"; break;
+		case T_BitmapIndexScan: return "BitmapIndexScan"; break;
+		case T_BitmapHeapScan: return "BitmapHeapScan"; break;
+		case T_TidScan: return "TidScan"; break;
+		case T_TidRangeScan: return "TidRangeScan"; break;
+		case T_SubqueryScan: return "SubqueryScan"; break;
+		case T_FunctionScan: return "FunctionScan"; break;
+		case T_ValuesScan: return "ValuesScan"; break;
+		case T_TableFuncScan: return "TableFuncScan"; break;
+		case T_CteScan: return "CteScan"; break;
+		case T_NamedTuplestoreScan: return "NamedTuplestoreScan"; break;
+		case T_WorkTableScan: return "WorkTableScan"; break;
+		case T_ForeignScan: return "ForeignScan"; break;
+		case T_CustomScan: return "CustomScan"; break;
+		case T_Join: return "Join"; break;
+		case T_NestLoop: return "NestLoop"; break;
+		case T_MergeJoin: return "MergeJoin"; break;
+		case T_HashJoin: return "HashJoin"; break;
+		case T_Material: return "Material"; break;
+		case T_Memoize: return "Memoize"; break;
+		case T_Sort: return "Sort"; break;
+		case T_IncrementalSort: return "IncrementalSort"; break;
+		case T_Group: return "Group"; break;
+		case T_Agg: return "Agg"; break;
+		case T_WindowAgg: return "WindowAgg"; break;
+		case T_Unique: return "Unique"; break;
+		case T_Gather: return "Gather"; break;
+		case T_GatherMerge: return "GatherMerge"; break;
+		case T_Hash: return "Hash"; break;
+		case T_SetOp: return "SetOp"; break;
+		case T_LockRows: return "LockRows"; break;
+		case T_Limit: return "Limit"; break;
+		case T_NestLoopParam: return "NestLoopParam"; break;
+		case T_PlanRowMark: return "PlanRowMark"; break;
+		case T_PartitionPruneInfo: return "PartitionPruneInfo"; break;
+		case T_PartitionedRelPruneInfo: return "PartitionedRelPruneInfo"; break;
+		case T_PartitionPruneStepOp: return "PartitionPruneStepOp"; break;
+		case T_PartitionPruneStepCombine: return "PartitionPruneStepCombine"; break;
+		case T_PlanInvalItem: return "PlanInvalItem"; break;
+		case T_PlanState: return "PlanState"; break;
+		case T_ResultState: return "ResultState"; break;
+		case T_ProjectSetState: return "ProjectSetState"; break;
+		case T_ModifyTableState: return "ModifyTableState"; break;
+		case T_AppendState: return "AppendState"; break;
+		case T_MergeAppendState: return "MergeAppendState"; break;
+		case T_RecursiveUnionState: return "RecursiveUnionState"; break;
+		case T_BitmapAndState: return "BitmapAndState"; break;
+		case T_BitmapOrState: return "BitmapOrState"; break;
+		case T_ScanState: return "ScanState"; break;
+		case T_SeqScanState: return "SeqScanState"; break;
+		case T_SampleScanState: return "SampleScanState"; break;
+		case T_IndexScanState: return "IndexScanState"; break;
+		case T_IndexOnlyScanState: return "IndexOnlyScanState"; break;
+		case T_BitmapIndexScanState: return "BitmapIndexScanState"; break;
+		case T_BitmapHeapScanState: return "BitmapHeapScanState"; break;
+		case T_TidScanState: return "TidScanState"; break;
+		case T_TidRangeScanState: return "TidRangeScanState"; break;
+		case T_SubqueryScanState: return "SubqueryScanState"; break;
+		case T_FunctionScanState: return "FunctionScanState"; break;
+		case T_TableFuncScanState: return "TableFuncScanState"; break;
+		case T_ValuesScanState: return "ValuesScanState"; break;
+		case T_CteScanState: return "CteScanState"; break;
+		case T_NamedTuplestoreScanState: return "NamedTuplestoreScanState"; break;
+		case T_WorkTableScanState: return "WorkTableScanState"; break;
+		case T_ForeignScanState: return "ForeignScanState"; break;
+		case T_CustomScanState: return "CustomScanState"; break;
+		case T_JoinState: return "JoinState"; break;
+		case T_NestLoopState: return "NestLoopState"; break;
+		case T_MergeJoinState: return "MergeJoinState"; break;
+		case T_HashJoinState: return "HashJoinState"; break;
+		case T_MaterialState: return "MaterialState"; break;
+		case T_MemoizeState: return "MemoizeState"; break;
+		case T_SortState: return "SortState"; break;
+		case T_IncrementalSortState: return "IncrementalSortState"; break;
+		case T_GroupState: return "GroupState"; break;
+		case T_AggState: return "AggState"; break;
+		case T_WindowAggState: return "WindowAggState"; break;
+		case T_UniqueState: return "UniqueState"; break;
+		case T_GatherState: return "GatherState"; break;
+		case T_GatherMergeState: return "GatherMergeState"; break;
+		case T_HashState: return "HashState"; break;
+		case T_SetOpState: return "SetOpState"; break;
+		case T_LockRowsState: return "LockRowsState"; break;
+		case T_LimitState: return "LimitState"; break;
+		case T_Alias: return "Alias"; break;
+		case T_RangeVar: return "RangeVar"; break;
+		case T_TableFunc: return "TableFunc"; break;
+		case T_Var: return "Var"; break;
+		case T_Const: return "Const"; break;
+		case T_Param: return "Param"; break;
+		case T_Aggref: return "Aggref"; break;
+		case T_GroupingFunc: return "GroupingFunc"; break;
+		case T_WindowFunc: return "WindowFunc"; break;
+		case T_SubscriptingRef: return "SubscriptingRef"; break;
+		case T_FuncExpr: return "FuncExpr"; break;
+		case T_NamedArgExpr: return "NamedArgExpr"; break;
+		case T_OpExpr: return "OpExpr"; break;
+		case T_DistinctExpr: return "DistinctExpr"; break;
+		case T_NullIfExpr: return "NullIfExpr"; break;
+		case T_ScalarArrayOpExpr: return "ScalarArrayOpExpr"; break;
+		case T_BoolExpr: return "BoolExpr"; break;
+		case T_SubLink: return "SubLink"; break;
+		case T_SubPlan: return "SubPlan"; break;
+		case T_AlternativeSubPlan: return "AlternativeSubPlan"; break;
+		case T_FieldSelect: return "FieldSelect"; break;
+		case T_FieldStore: return "FieldStore"; break;
+		case T_RelabelType: return "RelabelType"; break;
+		case T_CoerceViaIO: return "CoerceViaIO"; break;
+		case T_ArrayCoerceExpr: return "ArrayCoerceExpr"; break;
+		case T_ConvertRowtypeExpr: return "ConvertRowtypeExpr"; break;
+		case T_CollateExpr: return "CollateExpr"; break;
+		case T_CaseExpr: return "CaseExpr"; break;
+		case T_CaseWhen: return "CaseWhen"; break;
+		case T_CaseTestExpr: return "CaseTestExpr"; break;
+		case T_ArrayExpr: return "ArrayExpr"; break;
+		case T_RowExpr: return "RowExpr"; break;
+		case T_RowCompareExpr: return "RowCompareExpr"; break;
+		case T_CoalesceExpr: return "CoalesceExpr"; break;
+		case T_MinMaxExpr: return "MinMaxExpr"; break;
+		case T_SQLValueFunction: return "SQLValueFunction"; break;
+		case T_XmlExpr: return "XmlExpr"; break;
+		case T_NullTest: return "NullTest"; break;
+		case T_BooleanTest: return "BooleanTest"; break;
+		case T_CoerceToDomain: return "CoerceToDomain"; break;
+		case T_CoerceToDomainValue: return "CoerceToDomainValue"; break;
+		case T_SetToDefault: return "SetToDefault"; break;
+		case T_CurrentOfExpr: return "CurrentOfExpr"; break;
+		case T_NextValueExpr: return "NextValueExpr"; break;
+		case T_InferenceElem: return "InferenceElem"; break;
+		case T_TargetEntry: return "TargetEntry"; break;
+		case T_RangeTblRef: return "RangeTblRef"; break;
+		case T_JoinExpr: return "JoinExpr"; break;
+		case T_FromExpr: return "FromExpr"; break;
+		case T_OnConflictExpr: return "OnConflictExpr"; break;
+		case T_IntoClause: return "IntoClause"; break;
+		case T_ExprState: return "ExprState"; break;
+		case T_WindowFuncExprState: return "WindowFuncExprState"; break;
+		case T_SetExprState: return "SetExprState"; break;
+		case T_SubPlanState: return "SubPlanState"; break;
+		case T_DomainConstraintState: return "DomainConstraintState"; break;
+		case T_PlannerInfo: return "PlannerInfo"; break;
+		case T_PlannerGlobal: return "PlannerGlobal"; break;
+		case T_RelOptInfo: return "RelOptInfo"; break;
+		case T_IndexOptInfo: return "IndexOptInfo"; break;
+		case T_ForeignKeyOptInfo: return "ForeignKeyOptInfo"; break;
+		case T_ParamPathInfo: return "ParamPathInfo"; break;
+		case T_Path: return "Path"; break;
+		case T_IndexPath: return "IndexPath"; break;
+		case T_BitmapHeapPath: return "BitmapHeapPath"; break;
+		case T_BitmapAndPath: return "BitmapAndPath"; break;
+		case T_BitmapOrPath: return "BitmapOrPath"; break;
+		case T_TidPath: return "TidPath"; break;
+		case T_TidRangePath: return "TidRangePath"; break;
+		case T_SubqueryScanPath: return "SubqueryScanPath"; break;
+		case T_ForeignPath: return "ForeignPath"; break;
+		case T_CustomPath: return "CustomPath"; break;
+		case T_NestPath: return "NestPath"; break;
+		case T_MergePath: return "MergePath"; break;
+		case T_HashPath: return "HashPath"; break;
+		case T_AppendPath: return "AppendPath"; break;
+		case T_MergeAppendPath: return "MergeAppendPath"; break;
+		case T_GroupResultPath: return "GroupResultPath"; break;
+		case T_MaterialPath: return "MaterialPath"; break;
+		case T_MemoizePath: return "MemoizePath"; break;
+		case T_UniquePath: return "UniquePath"; break;
+		case T_GatherPath: return "GatherPath"; break;
+		case T_GatherMergePath: return "GatherMergePath"; break;
+		case T_ProjectionPath: return "ProjectionPath"; break;
+		case T_ProjectSetPath: return "ProjectSetPath"; break;
+		case T_SortPath: return "SortPath"; break;
+		case T_IncrementalSortPath: return "IncrementalSortPath"; break;
+		case T_GroupPath: return "GroupPath"; break;
+		case T_UpperUniquePath: return "UpperUniquePath"; break;
+		case T_AggPath: return "AggPath"; break;
+		case T_GroupingSetsPath: return "GroupingSetsPath"; break;
+		case T_MinMaxAggPath: return "MinMaxAggPath"; break;
+		case T_WindowAggPath: return "WindowAggPath"; break;
+		case T_SetOpPath: return "SetOpPath"; break;
+		case T_RecursiveUnionPath: return "RecursiveUnionPath"; break;
+		case T_LockRowsPath: return "LockRowsPath"; break;
+		case T_ModifyTablePath: return "ModifyTablePath"; break;
+		case T_LimitPath: return "LimitPath"; break;
+		case T_EquivalenceClass: return "EquivalenceClass"; break;
+		case T_EquivalenceMember: return "EquivalenceMember"; break;
+		case T_PathKey: return "PathKey"; break;
+		case T_PathTarget: return "PathTarget"; break;
+		case T_RestrictInfo: return "RestrictInfo"; break;
+		case T_IndexClause: return "IndexClause"; break;
+		case T_PlaceHolderVar: return "PlaceHolderVar"; break;
+		case T_SpecialJoinInfo: return "SpecialJoinInfo"; break;
+		case T_AppendRelInfo: return "AppendRelInfo"; break;
+		case T_RowIdentityVarInfo: return "RowIdentityVarInfo"; break;
+		case T_PlaceHolderInfo: return "PlaceHolderInfo"; break;
+		case T_MinMaxAggInfo: return "MinMaxAggInfo"; break;
+		case T_PlannerParamItem: return "PlannerParamItem"; break;
+		case T_RollupData: return "RollupData"; break;
+		case T_GroupingSetData: return "GroupingSetData"; break;
+		case T_StatisticExtInfo: return "StatisticExtInfo"; break;
+		case T_AllocSetContext: return "AllocSetContext"; break;
+		case T_SlabContext: return "SlabContext"; break;
+		case T_GenerationContext: return "GenerationContext"; break;
+		case T_Integer: return "Integer"; break;
+		case T_Float: return "Float"; break;
+		case T_Boolean: return "Boolean"; break;
+		case T_String: return "String"; break;
+		case T_BitString: return "BitString"; break;
+		case T_List: return "List"; break;
+		case T_IntList: return "IntList"; break;
+		case T_OidList: return "OidList"; break;
+		case T_ExtensibleNode: return "ExtensibleNode"; break;
+		case T_RawStmt: return "RawStmt"; break;
+		case T_Query: return "Query"; break;
+		case T_PlannedStmt: return "PlannedStmt"; break;
+		case T_InsertStmt: return "InsertStmt"; break;
+		case T_DeleteStmt: return "DeleteStmt"; break;
+		case T_UpdateStmt: return "UpdateStmt"; break;
+		case T_SelectStmt: return "SelectStmt"; break;
+		case T_ReturnStmt: return "ReturnStmt"; break;
+		case T_PLAssignStmt: return "PLAssignStmt"; break;
+		case T_AlterTableStmt: return "AlterTableStmt"; break;
+		case T_AlterTableCmd: return "AlterTableCmd"; break;
+		case T_AlterDomainStmt: return "AlterDomainStmt"; break;
+		case T_SetOperationStmt: return "SetOperationStmt"; break;
+		case T_GrantStmt: return "GrantStmt"; break;
+		case T_GrantRoleStmt: return "GrantRoleStmt"; break;
+		case T_AlterDefaultPrivilegesStmt: return "AlterDefaultPrivilegesStmt"; break;
+		case T_ClosePortalStmt: return "ClosePortalStmt"; break;
+		case T_ClusterStmt: return "ClusterStmt"; break;
+		case T_CopyStmt: return "CopyStmt"; break;
+		case T_CreateStmt: return "CreateStmt"; break;
+		case T_DefineStmt: return "DefineStmt"; break;
+		case T_DropStmt: return "DropStmt"; break;
+		case T_TruncateStmt: return "TruncateStmt"; break;
+		case T_CommentStmt: return "CommentStmt"; break;
+		case T_FetchStmt: return "FetchStmt"; break;
+		case T_IndexStmt: return "IndexStmt"; break;
+		case T_CreateFunctionStmt: return "CreateFunctionStmt"; break;
+		case T_AlterFunctionStmt: return "AlterFunctionStmt"; break;
+		case T_DoStmt: return "DoStmt"; break;
+		case T_RenameStmt: return "RenameStmt"; break;
+		case T_RuleStmt: return "RuleStmt"; break;
+		case T_NotifyStmt: return "NotifyStmt"; break;
+		case T_ListenStmt: return "ListenStmt"; break;
+		case T_UnlistenStmt: return "UnlistenStmt"; break;
+		case T_TransactionStmt: return "TransactionStmt"; break;
+		case T_ViewStmt: return "ViewStmt"; break;
+		case T_LoadStmt: return "LoadStmt"; break;
+		case T_CreateDomainStmt: return "CreateDomainStmt"; break;
+		case T_CreatedbStmt: return "CreatedbStmt"; break;
+		case T_DropdbStmt: return "DropdbStmt"; break;
+		case T_VacuumStmt: return "VacuumStmt"; break;
+		case T_ExplainStmt: return "ExplainStmt"; break;
+		case T_CreateTableAsStmt: return "CreateTableAsStmt"; break;
+		case T_CreateSeqStmt: return "CreateSeqStmt"; break;
+		case T_AlterSeqStmt: return "AlterSeqStmt"; break;
+		case T_VariableSetStmt: return "VariableSetStmt"; break;
+		case T_VariableShowStmt: return "VariableShowStmt"; break;
+		case T_DiscardStmt: return "DiscardStmt"; break;
+		case T_CreateTrigStmt: return "CreateTrigStmt"; break;
+		case T_CreatePLangStmt: return "CreatePLangStmt"; break;
+		case T_CreateRoleStmt: return "CreateRoleStmt"; break;
+		case T_AlterRoleStmt: return "AlterRoleStmt"; break;
+		case T_DropRoleStmt: return "DropRoleStmt"; break;
+		case T_LockStmt: return "LockStmt"; break;
+		case T_ConstraintsSetStmt: return "ConstraintsSetStmt"; break;
+		case T_ReindexStmt: return "ReindexStmt"; break;
+		case T_CheckPointStmt: return "CheckPointStmt"; break;
+		case T_CreateSchemaStmt: return "CreateSchemaStmt"; break;
+		case T_AlterDatabaseStmt: return "AlterDatabaseStmt"; break;
+		case T_AlterDatabaseRefreshCollStmt: return "AlterDatabaseRefreshCollStmt"; break;
+		case T_AlterDatabaseSetStmt: return "AlterDatabaseSetStmt"; break;
+		case T_AlterRoleSetStmt: return "AlterRoleSetStmt"; break;
+		case T_CreateConversionStmt: return "CreateConversionStmt"; break;
+		case T_CreateCastStmt: return "CreateCastStmt"; break;
+		case T_CreateOpClassStmt: return "CreateOpClassStmt"; break;
+		case T_CreateOpFamilyStmt: return "CreateOpFamilyStmt"; break;
+		case T_AlterOpFamilyStmt: return "AlterOpFamilyStmt"; break;
+		case T_PrepareStmt: return "PrepareStmt"; break;
+		case T_ExecuteStmt: return "ExecuteStmt"; break;
+		case T_DeallocateStmt: return "DeallocateStmt"; break;
+		case T_DeclareCursorStmt: return "DeclareCursorStmt"; break;
+		case T_CreateTableSpaceStmt: return "CreateTableSpaceStmt"; break;
+		case T_DropTableSpaceStmt: return "DropTableSpaceStmt"; break;
+		case T_AlterObjectDependsStmt: return "AlterObjectDependsStmt"; break;
+		case T_AlterObjectSchemaStmt: return "AlterObjectSchemaStmt"; break;
+		case T_AlterOwnerStmt: return "AlterOwnerStmt"; break;
+		case T_AlterOperatorStmt: return "AlterOperatorStmt"; break;
+		case T_AlterTypeStmt: return "AlterTypeStmt"; break;
+		case T_DropOwnedStmt: return "DropOwnedStmt"; break;
+		case T_ReassignOwnedStmt: return "ReassignOwnedStmt"; break;
+		case T_CompositeTypeStmt: return "CompositeTypeStmt"; break;
+		case T_CreateEnumStmt: return "CreateEnumStmt"; break;
+		case T_CreateRangeStmt: return "CreateRangeStmt"; break;
+		case T_AlterEnumStmt: return "AlterEnumStmt"; break;
+		case T_AlterTSDictionaryStmt: return "AlterTSDictionaryStmt"; break;
+		case T_AlterTSConfigurationStmt: return "AlterTSConfigurationStmt"; break;
+		case T_CreateFdwStmt: return "CreateFdwStmt"; break;
+		case T_AlterFdwStmt: return "AlterFdwStmt"; break;
+		case T_CreateForeignServerStmt: return "CreateForeignServerStmt"; break;
+		case T_AlterForeignServerStmt: return "AlterForeignServerStmt"; break;
+		case T_CreateUserMappingStmt: return "CreateUserMappingStmt"; break;
+		case T_AlterUserMappingStmt: return "AlterUserMappingStmt"; break;
+		case T_DropUserMappingStmt: return "DropUserMappingStmt"; break;
+		case T_AlterTableSpaceOptionsStmt: return "AlterTableSpaceOptionsStmt"; break;
+		case T_AlterTableMoveAllStmt: return "AlterTableMoveAllStmt"; break;
+		case T_SecLabelStmt: return "SecLabelStmt"; break;
+		case T_CreateForeignTableStmt: return "CreateForeignTableStmt"; break;
+		case T_ImportForeignSchemaStmt: return "ImportForeignSchemaStmt"; break;
+		case T_CreateExtensionStmt: return "CreateExtensionStmt"; break;
+		case T_AlterExtensionStmt: return "AlterExtensionStmt"; break;
+		case T_AlterExtensionContentsStmt: return "AlterExtensionContentsStmt"; break;
+		case T_CreateEventTrigStmt: return "CreateEventTrigStmt"; break;
+		case T_AlterEventTrigStmt: return "AlterEventTrigStmt"; break;
+		case T_RefreshMatViewStmt: return "RefreshMatViewStmt"; break;
+		case T_ReplicaIdentityStmt: return "ReplicaIdentityStmt"; break;
+		case T_AlterSystemStmt: return "AlterSystemStmt"; break;
+		case T_CreatePolicyStmt: return "CreatePolicyStmt"; break;
+		case T_AlterPolicyStmt: return "AlterPolicyStmt"; break;
+		case T_CreateTransformStmt: return "CreateTransformStmt"; break;
+		case T_CreateAmStmt: return "CreateAmStmt"; break;
+		case T_CreatePublicationStmt: return "CreatePublicationStmt"; break;
+		case T_AlterPublicationStmt: return "AlterPublicationStmt"; break;
+		case T_CreateSubscriptionStmt: return "CreateSubscriptionStmt"; break;
+		case T_AlterSubscriptionStmt: return "AlterSubscriptionStmt"; break;
+		case T_DropSubscriptionStmt: return "DropSubscriptionStmt"; break;
+		case T_CreateStatsStmt: return "CreateStatsStmt"; break;
+		case T_AlterCollationStmt: return "AlterCollationStmt"; break;
+		case T_CallStmt: return "CallStmt"; break;
+		case T_AlterStatsStmt: return "AlterStatsStmt"; break;
+		case T_A_Expr: return "A_Expr"; break;
+		case T_ColumnRef: return "ColumnRef"; break;
+		case T_ParamRef: return "ParamRef"; break;
+		case T_A_Const: return "A_Const"; break;
+		case T_FuncCall: return "FuncCall"; break;
+		case T_A_Star: return "A_Star"; break;
+		case T_A_Indices: return "A_Indices"; break;
+		case T_A_Indirection: return "A_Indirection"; break;
+		case T_A_ArrayExpr: return "A_ArrayExpr"; break;
+		case T_ResTarget: return "ResTarget"; break;
+		case T_MultiAssignRef: return "MultiAssignRef"; break;
+		case T_TypeCast: return "TypeCast"; break;
+		case T_CollateClause: return "CollateClause"; break;
+		case T_SortBy: return "SortBy"; break;
+		case T_WindowDef: return "WindowDef"; break;
+		case T_RangeSubselect: return "RangeSubselect"; break;
+		case T_RangeFunction: return "RangeFunction"; break;
+		case T_RangeTableSample: return "RangeTableSample"; break;
+		case T_RangeTableFunc: return "RangeTableFunc"; break;
+		case T_RangeTableFuncCol: return "RangeTableFuncCol"; break;
+		case T_TypeName: return "TypeName"; break;
+		case T_ColumnDef: return "ColumnDef"; break;
+		case T_IndexElem: return "IndexElem"; break;
+		case T_StatsElem: return "StatsElem"; break;
+		case T_Constraint: return "Constraint"; break;
+		case T_DefElem: return "DefElem"; break;
+		case T_RangeTblEntry: return "RangeTblEntry"; break;
+		case T_RangeTblFunction: return "RangeTblFunction"; break;
+		case T_TableSampleClause: return "TableSampleClause"; break;
+		case T_WithCheckOption: return "WithCheckOption"; break;
+		case T_SortGroupClause: return "SortGroupClause"; break;
+		case T_GroupingSet: return "GroupingSet"; break;
+		case T_WindowClause: return "WindowClause"; break;
+		case T_ObjectWithArgs: return "ObjectWithArgs"; break;
+		case T_AccessPriv: return "AccessPriv"; break;
+		case T_CreateOpClassItem: return "CreateOpClassItem"; break;
+		case T_TableLikeClause: return "TableLikeClause"; break;
+		case T_FunctionParameter: return "FunctionParameter"; break;
+		case T_LockingClause: return "LockingClause"; break;
+		case T_RowMarkClause: return "RowMarkClause"; break;
+		case T_XmlSerialize: return "XmlSerialize"; break;
+		case T_WithClause: return "WithClause"; break;
+		case T_InferClause: return "InferClause"; break;
+		case T_OnConflictClause: return "OnConflictClause"; break;
+		case T_CTESearchClause: return "CTESearchClause"; break;
+		case T_CTECycleClause: return "CTECycleClause"; break;
+		case T_CommonTableExpr: return "CommonTableExpr"; break;
+		case T_RoleSpec: return "RoleSpec"; break;
+		case T_TriggerTransition: return "TriggerTransition"; break;
+		case T_PartitionElem: return "PartitionElem"; break;
+		case T_PartitionSpec: return "PartitionSpec"; break;
+		case T_PartitionBoundSpec: return "PartitionBoundSpec"; break;
+		case T_PartitionRangeDatum: return "PartitionRangeDatum"; break;
+		case T_PartitionCmd: return "PartitionCmd"; break;
+		case T_VacuumRelation: return "VacuumRelation"; break;
+		case T_PublicationObjSpec: return "PublicationObjSpec"; break;
+		case T_PublicationTable: return "PublicationTable"; break;
+		case T_IdentifySystemCmd: return "IdentifySystemCmd"; break;
+		case T_BaseBackupCmd: return "BaseBackupCmd"; break;
+		case T_CreateReplicationSlotCmd: return "CreateReplicationSlotCmd"; break;
+		case T_DropReplicationSlotCmd: return "DropReplicationSlotCmd"; break;
+		case T_ReadReplicationSlotCmd: return "ReadReplicationSlotCmd"; break;
+		case T_StartReplicationCmd: return "StartReplicationCmd"; break;
+		case T_TimeLineHistoryCmd: return "TimeLineHistoryCmd"; break;
+		case T_TriggerData: return "TriggerData"; break;
+		case T_EventTriggerData: return "EventTriggerData"; break;
+		case T_ReturnSetInfo: return "ReturnSetInfo"; break;
+		case T_WindowObjectData: return "WindowObjectData"; break;
+		case T_TIDBitmap: return "TIDBitmap"; break;
+		case T_InlineCodeBlock: return "InlineCodeBlock"; break;
+		case T_FdwRoutine: return "FdwRoutine"; break;
+		case T_IndexAmRoutine: return "IndexAmRoutine"; break;
+		case T_TableAmRoutine: return "TableAmRoutine"; break;
+		case T_TsmRoutine: return "TsmRoutine"; break;
+		case T_ForeignKeyCacheInfo: return "ForeignKeyCacheInfo"; break;
+		case T_CallContext: return "CallContext"; break;
+		case T_SupportRequestSimplify: return "SupportRequestSimplify"; break;
+		case T_SupportRequestSelectivity: return "SupportRequestSelectivity"; break;
+		case T_SupportRequestCost: return "SupportRequestCost"; break;
+		case T_SupportRequestRows: return "SupportRequestRows"; break;
+		case T_SupportRequestIndexCondition: return "SupportRequestIndexCondition"; break;
+		default:
+			break;
+	}
+	return "UNRECOGNIZED NodeTag";
+}
+
+static char *
+accesstype_to_string(ObjectAccessType access, int subId)
+{
+	const char *type;
+
+	switch (access)
+	{
+		case OAT_POST_CREATE:
+			type = "create";
+			break;
+		case OAT_DROP:
+			type = "drop";
+			break;
+		case OAT_POST_ALTER:
+			type = "alter";
+			break;
+		case OAT_NAMESPACE_SEARCH:
+			type = "namespace search";
+			break;
+		case OAT_FUNCTION_EXECUTE:
+			type = "execute";
+			break;
+		case OAT_TRUNCATE:
+			type = "truncate";
+			break;
+		default:
+			type = "UNRECOGNIZED ObjectAccessType";
+	}
+
+	if (subId & ACL_SET_VALUE)
+		return psprintf("%s (set)", type);
+	if (subId & ACL_ALTER_SYSTEM)
+		return psprintf("%s (alter system set)", type);
+
+	return  psprintf("%s (subId=%d)", type, subId);
+}
+
+static char *
+accesstype_arg_to_string(ObjectAccessType access, void *arg)
+{
+	if (arg == NULL)
+		return pstrdup("extra info null");
+
+	switch (access)
+	{
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *)arg;
+				return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
+			}
+			break;
+		case OAT_DROP:
+			{
+				ObjectAccessDrop *drop_arg = (ObjectAccessDrop *)arg;
+
+				return psprintf("%s%s%s%s%s%s",
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "internal action," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "concurrent drop," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "suppress notices," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "keep original object," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "keep extensions," : ""),
+					((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
+						? "normal concurrent drop," : ""));
+			}
+			break;
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter*)arg;
+
+				return psprintf("%s %s auxiliary object",
+					(pa_arg->is_internal ? "internal" : "explicit"),
+					(OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
+			}
+			break;
+		case OAT_NAMESPACE_SEARCH:
+			{
+				ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *)arg;
+
+				return psprintf("%s, %s",
+					(ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
+					(ns_arg->result ? "allowed" : "denied"));
+			}
+			break;
+		case OAT_TRUNCATE:
+		case OAT_FUNCTION_EXECUTE:
+			/* hook takes no arg. */
+			return pstrdup("unexpected extra info pointer received");
+		default:
+			return pstrdup("cannot parse extra info for unrecognized access type");
+	}
+
+	return pstrdup("unknown");
+}
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.conf b/src/test/modules/test_oat_hooks/test_oat_hooks.conf
new file mode 100644
index 0000000000..a44cbdd4a4
--- /dev/null
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.conf
@@ -0,0 +1 @@
+shared_preload_libraries = test_oat_hooks
-- 
2.35.1

#10Thomas Munro
thomas.munro@gmail.com
In reply to: Mark Dilger (#1)
Re: New Object Access Type hooks

On Fri, Mar 18, 2022 at 4:22 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:

(FYI, I got a test failure from src/test/recovery/t/013_crash_restart.pl when testing v1-0001. I'm not sure yet what that is about.)

Doesn't look like 0001 has anything to do with that... Are you on a
Mac? Did it look like this recent failure from CI?

https://cirrus-ci.com/task/4686108033286144
https://api.cirrus-ci.com/v1/artifact/task/4686108033286144/log/src/test/recovery/tmp_check/log/regress_log_013_crash_restart
https://api.cirrus-ci.com/v1/artifact/task/4686108033286144/log/src/test/recovery/tmp_check/log/013_crash_restart_primary.log

I have no idea what is going on there, but searching for discussion
brought me here...

#11Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Thomas Munro (#10)
Re: New Object Access Type hooks

On Mar 21, 2022, at 10:03 PM, Thomas Munro <thomas.munro@gmail.com> wrote:

On Fri, Mar 18, 2022 at 4:22 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:

(FYI, I got a test failure from src/test/recovery/t/013_crash_restart.pl when testing v1-0001. I'm not sure yet what that is about.)

Doesn't look like 0001 has anything to do with that... Are you on a
Mac?

Yes, macOS Catalina, currently 10.15.7.

Did it look like this recent failure from CI?

https://cirrus-ci.com/task/4686108033286144
https://api.cirrus-ci.com/v1/artifact/task/4686108033286144/log/src/test/recovery/tmp_check/log/regress_log_013_crash_restart
https://api.cirrus-ci.com/v1/artifact/task/4686108033286144/log/src/test/recovery/tmp_check/log/013_crash_restart_primary.log

I no longer have the logs for comparison.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Andrew Dunstan
andrew@dunslane.net
In reply to: Thomas Munro (#10)
Re: New Object Access Type hooks

On 3/22/22 01:03, Thomas Munro wrote:

On Fri, Mar 18, 2022 at 4:22 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:

(FYI, I got a test failure from src/test/recovery/t/013_crash_restart.pl when testing v1-0001. I'm not sure yet what that is about.)

Doesn't look like 0001 has anything to do with that... Are you on a
Mac? Did it look like this recent failure from CI?

Probably not connected. It's working fine for me on Ubuntu/

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#13Andrew Dunstan
andrew@dunslane.net
In reply to: Mark Dilger (#9)
Re: New Object Access Type hooks

On 3/21/22 19:08, Mark Dilger wrote:

On Mar 21, 2022, at 1:30 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

To the best of my knowledge .control files are only used by extensions,
not by other modules. They are only referenced in
src/backend/commands/extension.c in the backend code. For example,
auto_explain which is a loadable module but not en extension does not
have one, and I bet if you remove it you'll find this will work just fine.

Fixed, also with adjustments to Joshua's function comments.

Pushed with slight adjustments - the LOAD was unnecessary as was the
setting of client_min_messages - the latter would have made buildfarm
animals unhappy.

Now you need to re-submit your GUCs patch I think.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#14Julien Rouhaud
rjuju123@gmail.com
In reply to: Andrew Dunstan (#13)
Re: New Object Access Type hooks

Hi,

On Tue, Mar 22, 2022 at 10:41:05AM -0400, Andrew Dunstan wrote:

Pushed with slight adjustments - the LOAD was unnecessary as was the
setting of client_min_messages - the latter would have made buildfarm
animals unhappy.

For the record this just failed on my buildfarm animal:
https://brekka.postgresql.org/cgi-bin/show_stage_log.pl?nm=lapwing&amp;dt=2022-03-22%2014%3A40%3A10&amp;stg=misc-check.

#15Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Julien Rouhaud (#14)
Re: New Object Access Type hooks

On Mar 22, 2022, at 8:14 AM, Julien Rouhaud <rjuju123@gmail.com> wrote:

Hi,

On Tue, Mar 22, 2022 at 10:41:05AM -0400, Andrew Dunstan wrote:

Pushed with slight adjustments - the LOAD was unnecessary as was the
setting of client_min_messages - the latter would have made buildfarm
animals unhappy.

For the record this just failed on my buildfarm animal:
https://brekka.postgresql.org/cgi-bin/show_stage_log.pl?nm=lapwing&amp;dt=2022-03-22%2014%3A40%3A10&amp;stg=misc-check.

culicidae is complaining:

==~_~===-=-===~_~== pgsql.build/src/test/modules/test_oat_hooks/log/postmaster.log ==~_~===-=-===~_~==
2022-03-22 14:53:27.175 UTC [2166986][postmaster][:0] LOG: starting PostgreSQL 15devel on x86_64-pc-linux-gnu, compiled by gcc (Debian 11.2.0-18) 11.2.0, 64-bit
2022-03-22 14:53:27.175 UTC [2166986][postmaster][:0] LOG: listening on Unix socket "/tmp/pg_regress-RiE7x8/.s.PGSQL.6280"
2022-03-22 14:53:27.198 UTC [2167008][not initialized][:0] FATAL: test_oat_hooks must be loaded via shared_preload_libraries
2022-03-22 14:53:27.202 UTC [2167006][not initialized][:0] FATAL: test_oat_hooks must be loaded via shared_preload_libraries
2022-03-22 14:53:27.203 UTC [2167009][not initialized][:0] FATAL: test_oat_hooks must be loaded via shared_preload_libraries
2022-03-22 14:53:27.204 UTC [2166986][postmaster][:0] LOG: checkpointer process (PID 2167006) exited with exit code 1
2022-03-22 14:53:27.204 UTC [2166986][postmaster][:0] LOG: terminating any other active server processes
2022-03-22 14:53:27.204 UTC [2166986][postmaster][:0] LOG: shutting down because restart_after_crash is off
2022-03-22 14:53:27.206 UTC [2166986][postmaster][:0] LOG: database system is shut down
==~_~===-=-===~_~== pgsql.build/src/test/modules/test_rls_hooks/log/initdb.log ==~_~===-=-===~_~==


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#16Andrew Dunstan
andrew@dunslane.net
In reply to: Mark Dilger (#15)
Re: New Object Access Type hooks

On 3/22/22 11:26, Mark Dilger wrote:

On Mar 22, 2022, at 8:14 AM, Julien Rouhaud <rjuju123@gmail.com> wrote:

Hi,

On Tue, Mar 22, 2022 at 10:41:05AM -0400, Andrew Dunstan wrote:

Pushed with slight adjustments - the LOAD was unnecessary as was the
setting of client_min_messages - the latter would have made buildfarm
animals unhappy.

For the record this just failed on my buildfarm animal:
https://brekka.postgresql.org/cgi-bin/show_stage_log.pl?nm=lapwing&amp;dt=2022-03-22%2014%3A40%3A10&amp;stg=misc-check.

culicidae is complaining:

==~_~===-=-===~_~== pgsql.build/src/test/modules/test_oat_hooks/log/postmaster.log ==~_~===-=-===~_~==
2022-03-22 14:53:27.175 UTC [2166986][postmaster][:0] LOG: starting PostgreSQL 15devel on x86_64-pc-linux-gnu, compiled by gcc (Debian 11.2.0-18) 11.2.0, 64-bit
2022-03-22 14:53:27.175 UTC [2166986][postmaster][:0] LOG: listening on Unix socket "/tmp/pg_regress-RiE7x8/.s.PGSQL.6280"
2022-03-22 14:53:27.198 UTC [2167008][not initialized][:0] FATAL: test_oat_hooks must be loaded via shared_preload_libraries
2022-03-22 14:53:27.202 UTC [2167006][not initialized][:0] FATAL: test_oat_hooks must be loaded via shared_preload_libraries
2022-03-22 14:53:27.203 UTC [2167009][not initialized][:0] FATAL: test_oat_hooks must be loaded via shared_preload_libraries
2022-03-22 14:53:27.204 UTC [2166986][postmaster][:0] LOG: checkpointer process (PID 2167006) exited with exit code 1
2022-03-22 14:53:27.204 UTC [2166986][postmaster][:0] LOG: terminating any other active server processes
2022-03-22 14:53:27.204 UTC [2166986][postmaster][:0] LOG: shutting down because restart_after_crash is off
2022-03-22 14:53:27.206 UTC [2166986][postmaster][:0] LOG: database system is shut down
==~_~===-=-===~_~== pgsql.build/src/test/modules/test_rls_hooks/log/initdb.log ==~_~===-=-===~_~==

That seems quite weird. I'm not sure how it's getting loaded at all if
not via shared_preload_libraries

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#16)
Re: New Object Access Type hooks

Andrew Dunstan <andrew@dunslane.net> writes:

That seems quite weird. I'm not sure how it's getting loaded at all if
not via shared_preload_libraries

Some other animals are showing this:

diff -U3 /home/postgres/pgsql/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out /home/postgres/pgsql/src/test/modules/test_oat_hooks/results/test_oat_hooks.out
--- /home/postgres/pgsql/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out	2022-03-22 11:57:40.224991011 -0400
+++ /home/postgres/pgsql/src/test/modules/test_oat_hooks/results/test_oat_hooks.out	2022-03-22 11:59:59.998983366 -0400
@@ -48,6 +48,8 @@
 SELECT * FROM regress_test_table;
 NOTICE:  in executor check perms: superuser attempting execute
 NOTICE:  in executor check perms: superuser finished execute
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
  t 
 ---
 (0 rows)
@@ -95,6 +97,8 @@
                       ^
 NOTICE:  in executor check perms: non-superuser attempting execute
 NOTICE:  in executor check perms: non-superuser finished execute
+NOTICE:  in executor check perms: non-superuser attempting execute
+NOTICE:  in executor check perms: non-superuser finished execute
  t 
 ---
 (0 rows)
@@ -168,6 +172,8 @@
                       ^
 NOTICE:  in executor check perms: superuser attempting execute
 NOTICE:  in executor check perms: superuser finished execute
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
  t 
 ---
 (0 rows)

I can duplicate that by adding "force_parallel_mode = regress"
to test_oat_hooks.conf, so a fair bet is that the duplication
comes from executing the same hook in both leader and worker.

regards, tom lane

#18Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#17)
Re: New Object Access Type hooks

On 3/22/22 12:02, Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

That seems quite weird. I'm not sure how it's getting loaded at all if
not via shared_preload_libraries

Some other animals are showing this:

diff -U3 /home/postgres/pgsql/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out /home/postgres/pgsql/src/test/modules/test_oat_hooks/results/test_oat_hooks.out
--- /home/postgres/pgsql/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out	2022-03-22 11:57:40.224991011 -0400
+++ /home/postgres/pgsql/src/test/modules/test_oat_hooks/results/test_oat_hooks.out	2022-03-22 11:59:59.998983366 -0400
@@ -48,6 +48,8 @@
SELECT * FROM regress_test_table;
NOTICE:  in executor check perms: superuser attempting execute
NOTICE:  in executor check perms: superuser finished execute
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
t 
---
(0 rows)
@@ -95,6 +97,8 @@
^
NOTICE:  in executor check perms: non-superuser attempting execute
NOTICE:  in executor check perms: non-superuser finished execute
+NOTICE:  in executor check perms: non-superuser attempting execute
+NOTICE:  in executor check perms: non-superuser finished execute
t 
---
(0 rows)
@@ -168,6 +172,8 @@
^
NOTICE:  in executor check perms: superuser attempting execute
NOTICE:  in executor check perms: superuser finished execute
+NOTICE:  in executor check perms: superuser attempting execute
+NOTICE:  in executor check perms: superuser finished execute
t 
---
(0 rows)

I can duplicate that by adding "force_parallel_mode = regress"
to test_oat_hooks.conf, so a fair bet is that the duplication
comes from executing the same hook in both leader and worker.

OK, thanks. My test didn't include that one setting :-(

If I can't com up with a very quick fix I'll revert it.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#19Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Andrew Dunstan (#18)
Re: New Object Access Type hooks

On Mar 22, 2022, at 9:09 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

If I can't com up with a very quick fix I'll revert it.

The problem is coming from the REGRESS_exec_check_perms, which was included in the patch to demonstrate when the other hooks fired relative to the ExecutorCheckPerms_hook, but since it is causing problems, I can submit a patch with that removed. Give me a couple minutes....


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#20Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Mark Dilger (#19)
1 attachment(s)
Re: New Object Access Type hooks
Show quoted text

On Mar 22, 2022, at 9:11 AM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:

Give me a couple minutes....

Attachments:

v1-0001-Fix-build-farm-failures-in-test_oat_hooks.patchapplication/octet-stream; name=v1-0001-Fix-build-farm-failures-in-test_oat_hooks.patch; x-unix-mode=0644Download
From 669b633dadb8ad7e3ebab373e68e63840454e8ae Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 22 Mar 2022 09:13:58 -0700
Subject: [PATCH v1] Fix build farm failures in test_oat_hooks

The behavior of this regression test was unstable.  Fix that.
---
 .../expected/test_oat_hooks.out               | 12 -----
 .../modules/test_oat_hooks/test_oat_hooks.c   | 47 -------------------
 2 files changed, 59 deletions(-)

diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
index 2035769580..07247b4b24 100644
--- a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
+++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
@@ -46,15 +46,11 @@ NOTICE:  in process utility: superuser attempting GrantStmt
 NOTICE:  in process utility: superuser finished GrantStmt
 -- Do a few things as superuser
 SELECT * FROM regress_test_table;
-NOTICE:  in executor check perms: superuser attempting execute
-NOTICE:  in executor check perms: superuser finished execute
  t 
 ---
 (0 rows)
 
 SELECT regress_test_func('arg');
-NOTICE:  in executor check perms: superuser attempting execute
-NOTICE:  in executor check perms: superuser finished execute
  regress_test_func 
 -------------------
  arg
@@ -93,15 +89,11 @@ LINE 1: SELECT * FROM regress_test_table;
 NOTICE:  in object access: non-superuser finished namespace search (subId=0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
-NOTICE:  in executor check perms: non-superuser attempting execute
-NOTICE:  in executor check perms: non-superuser finished execute
  t 
 ---
 (0 rows)
 
 SELECT regress_test_func('arg');
-NOTICE:  in executor check perms: non-superuser attempting execute
-NOTICE:  in executor check perms: non-superuser finished execute
  regress_test_func 
 -------------------
  arg
@@ -166,15 +158,11 @@ LINE 1: SELECT * FROM regress_test_table;
 NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
-NOTICE:  in executor check perms: superuser attempting execute
-NOTICE:  in executor check perms: superuser finished execute
  t 
 ---
 (0 rows)
 
 SELECT regress_test_func('arg');
-NOTICE:  in executor check perms: superuser attempting execute
-NOTICE:  in executor check perms: superuser finished execute
  regress_test_func 
 -------------------
  arg
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index b1709f4d63..ed2c79652e 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -36,7 +36,6 @@ static bool REGRESS_audit = false;
 /* Saved hook values in case of unload */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -45,7 +44,6 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -171,10 +169,6 @@ _PG_init(void)
 	next_object_access_hook_str = object_access_hook_str;
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
-	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
-
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
 	ProcessUtility_hook = REGRESS_utility_command;
@@ -190,9 +184,6 @@ _PG_fini(void)
 	if (object_access_hook_str == REGRESS_object_access_hook_str)
 		object_access_hook_str = next_object_access_hook_str;
 
-	if (ExecutorCheckPerms_hook == REGRESS_exec_check_perms)
-		ExecutorCheckPerms_hook = next_exec_check_perms_hook;
-
 	if (ProcessUtility_hook == REGRESS_utility_command)
 		ProcessUtility_hook = next_ProcessUtility_hook;
 }
@@ -232,12 +223,6 @@ audit_success(const char *hook, char *action, char *objName)
 	emit_audit_message("finished", hook, action, objName);
 }
 
-static void
-audit_failure(const char *hook, char *action, char *objName)
-{
-	emit_audit_message("denied", hook, action, objName);
-}
-
 static void
 REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
 {
@@ -302,38 +287,6 @@ REGRESS_object_access_hook (ObjectAccessType access, Oid classId, Oid objectId,
 				  accesstype_arg_to_string(access, arg));
 }
 
-static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
-{
-	bool		am_super = superuser_arg(GetUserId());
-	bool		allow = true;
-
-	audit_attempt("executor check perms", pstrdup("execute"), NULL);
-
-	/* Perform our check */
-	allow = !REGRESS_deny_exec_perms || am_super;
-	if (do_abort && !allow)
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("permission denied: %s", "execute")));
-
-	/* Forward to next hook in the chain */
-	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
-		allow = false;
-
-	if (allow)
-		audit_success("executor check perms",
-					  pstrdup("execute"),
-					  NULL);
-	else
-		audit_failure("executor check perms",
-					  pstrdup("execute"),
-					  NULL);
-
-	return allow;
-}
-
 static void
 REGRESS_utility_command(PlannedStmt *pstmt,
 					  const char *queryString,
-- 
2.35.1

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Mark Dilger (#19)
Re: New Object Access Type hooks

Mark Dilger <mark.dilger@enterprisedb.com> writes:

The problem is coming from the REGRESS_exec_check_perms, which was included in the patch to demonstrate when the other hooks fired relative to the ExecutorCheckPerms_hook, but since it is causing problems, I can submit a patch with that removed. Give me a couple minutes....

Maybe better to suppress the audit messages if in a parallel worker?

regards, tom lane

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#16)
Re: New Object Access Type hooks

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/22/22 11:26, Mark Dilger wrote:

culicidae is complaining:
2022-03-22 14:53:27.198 UTC [2167008][not initialized][:0] FATAL: test_oat_hooks must be loaded via shared_preload_libraries

That seems quite weird. I'm not sure how it's getting loaded at all if
not via shared_preload_libraries

After checking culicidae's config, I've duplicated this failure
by building with EXEC_BACKEND defined. So I'd opine that there
is something broken about the method test_oat_hooks uses to
decide if it was loaded via shared_preload_libraries or not.
(Note that the failures appear to be coming out of auxiliary
processes such as the checkpointer.)

As a quick-n-dirty fix to avoid reverting the entire test module,
perhaps just delete this error check for now.

regards, tom lane

#23Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Tom Lane (#22)
1 attachment(s)
Re: New Object Access Type hooks

On Mar 22, 2022, at 9:33 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/22/22 11:26, Mark Dilger wrote:

culicidae is complaining:
2022-03-22 14:53:27.198 UTC [2167008][not initialized][:0] FATAL: test_oat_hooks must be loaded via shared_preload_libraries

That seems quite weird. I'm not sure how it's getting loaded at all if
not via shared_preload_libraries

After checking culicidae's config, I've duplicated this failure
by building with EXEC_BACKEND defined. So I'd opine that there
is something broken about the method test_oat_hooks uses to
decide if it was loaded via shared_preload_libraries or not.
(Note that the failures appear to be coming out of auxiliary
processes such as the checkpointer.)

As a quick-n-dirty fix to avoid reverting the entire test module,
perhaps just delete this error check for now.

Ok, done as you suggest:

Attachments:

v2-0001-Fix-buildfarm-test-failures-in-test_oat_hooks.patchapplication/octet-stream; name=v2-0001-Fix-buildfarm-test-failures-in-test_oat_hooks.patch; x-unix-mode=0644Download
From 862e79a913de8754c7d4523e2495ba703da25c4e Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 22 Mar 2022 09:46:32 -0700
Subject: [PATCH v2] Fix buildfarm test failures in test_oat_hooks

---
 src/test/modules/test_oat_hooks/Makefile             |  3 ---
 .../test_oat_hooks/expected/test_oat_hooks.out       |  1 +
 .../modules/test_oat_hooks/sql/test_oat_hooks.sql    |  1 +
 src/test/modules/test_oat_hooks/test_oat_hooks.c     | 12 ++----------
 src/test/modules/test_oat_hooks/test_oat_hooks.conf  |  1 -
 5 files changed, 4 insertions(+), 14 deletions(-)
 delete mode 100644 src/test/modules/test_oat_hooks/test_oat_hooks.conf

diff --git a/src/test/modules/test_oat_hooks/Makefile b/src/test/modules/test_oat_hooks/Makefile
index d0df9c1abb..d03ef2caf1 100644
--- a/src/test/modules/test_oat_hooks/Makefile
+++ b/src/test/modules/test_oat_hooks/Makefile
@@ -7,9 +7,6 @@ OBJS = \
 PGFILEDESC = "test_oat_hooks - example use of object access hooks"
 
 REGRESS = test_oat_hooks
-REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_oat_hooks/test_oat_hooks.conf
-# Disabled because these tests require "shared_preload_libraries=test_oat_hooks",
-# which typical installcheck users do not have (e.g. buildfarm clients).
 NO_INSTALLCHECK = 1
 
 ifdef USE_PGXS
diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
index 2035769580..4192a2fb49 100644
--- a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
+++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
@@ -2,6 +2,7 @@
 -- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
 -- initial "attempting" audit message from the ProcessUtility_hook, but we
 -- should thereafter see the audit messages
+LOAD 'test_oat_hooks';
 SET test_oat_hooks.audit = true;
 NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.audit]
 NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.audit]
diff --git a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
index 5fb3a2f0ea..7c38202782 100644
--- a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
+++ b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
@@ -2,6 +2,7 @@
 -- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
 -- initial "attempting" audit message from the ProcessUtility_hook, but we
 -- should thereafter see the audit messages
+LOAD 'test_oat_hooks';
 SET test_oat_hooks.audit = true;
 
 -- Create objects for use in the test
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index b1709f4d63..b50567749c 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -13,6 +13,7 @@
 
 #include "postgres.h"
 
+#include "access/parallel.h"
 #include "catalog/dependency.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_proc.h"
@@ -68,15 +69,6 @@ void		_PG_fini(void);
 void
 _PG_init(void)
 {
-	/*
-	 * We allow to load the Object Access Type test module on single-user-mode
-	 * or shared_preload_libraries settings only.
-	 */
-	if (IsUnderPostmaster)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("test_oat_hooks must be loaded via shared_preload_libraries")));
-
 	/*
 	 * test_oat_hooks.deny_set_variable = (on|off)
 	 */
@@ -200,7 +192,7 @@ _PG_fini(void)
 static void
 emit_audit_message(const char *type, const char *hook, char *action, char *objName)
 {
-	if (REGRESS_audit)
+	if (REGRESS_audit && !IsParallelWorker())
 	{
 		const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
 
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.conf b/src/test/modules/test_oat_hooks/test_oat_hooks.conf
deleted file mode 100644
index a44cbdd4a4..0000000000
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.conf
+++ /dev/null
@@ -1 +0,0 @@
-shared_preload_libraries = test_oat_hooks
-- 
2.35.1

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Mark Dilger (#23)
Re: New Object Access Type hooks

Mark Dilger <mark.dilger@enterprisedb.com> writes:

On Mar 22, 2022, at 9:33 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

As a quick-n-dirty fix to avoid reverting the entire test module,
perhaps just delete this error check for now.

Ok, done as you suggest:

I only suggested removing the error check in _PG_init, not
changing the way the test works.

regards, tom lane

#25Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#24)
Re: New Object Access Type hooks

On 3/22/22 12:58, Tom Lane wrote:

Mark Dilger <mark.dilger@enterprisedb.com> writes:

On Mar 22, 2022, at 9:33 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

As a quick-n-dirty fix to avoid reverting the entire test module,
perhaps just delete this error check for now.

Ok, done as you suggest:

I only suggested removing the error check in _PG_init, not
changing the way the test works.

Mark and I discussed this offline, and decided there was no requirement
for the module to be preloaded. Do you have a different opinion?

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#26Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Tom Lane (#24)
Re: New Object Access Type hooks

On Mar 22, 2022, at 9:58 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Ok, done as you suggest:

I only suggested removing the error check in _PG_init, not
changing the way the test works.

I should have been more explicit and said, "done as y'all suggest". The "To" line of that email was to you and Andrew.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#25)
Re: New Object Access Type hooks

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/22/22 12:58, Tom Lane wrote:

I only suggested removing the error check in _PG_init, not
changing the way the test works.

Mark and I discussed this offline, and decided there was no requirement
for the module to be preloaded. Do you have a different opinion?

No, I was actually about to make the same point: it seems to me there
are arguable use-cases for loading it shared, loading it per-session
(perhaps via ALTER USER SET or ALTER DATABASE SET to target particular
users/DBs), or even manually LOADing it. So the module code should
not be prejudging how it's used.

On reflection, I withdraw my complaint about changing the way the
test script loads the module. Getting rid of the need for a custom
.conf file simplifies the test module, and that seems good.
So I'm on board with Mark's patch now.

regards, tom lane

#28Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#27)
Re: New Object Access Type hooks

On 3/22/22 13:08, Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/22/22 12:58, Tom Lane wrote:

I only suggested removing the error check in _PG_init, not
changing the way the test works.

Mark and I discussed this offline, and decided there was no requirement
for the module to be preloaded. Do you have a different opinion?

No, I was actually about to make the same point: it seems to me there
are arguable use-cases for loading it shared, loading it per-session
(perhaps via ALTER USER SET or ALTER DATABASE SET to target particular
users/DBs), or even manually LOADing it. So the module code should
not be prejudging how it's used.

On reflection, I withdraw my complaint about changing the way the
test script loads the module. Getting rid of the need for a custom
.conf file simplifies the test module, and that seems good.
So I'm on board with Mark's patch now.

OK, I have pushed that.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#29Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#28)
Re: New Object Access Type hooks

Andrew Dunstan <andrew@dunslane.net> writes:

OK, I have pushed that.

It seems like you could remove the NO_INSTALLCHECK restriction
too. You already removed the comment defending it, and it
seems to work fine as an installcheck now if I remove that
locally.

Other nitpicks:

* the IsParallelWorker test could use a comment

* I notice a typo "permisisons" in test_oat_hooks.sql

regards, tom lane

#30Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#29)
Re: New Object Access Type hooks

On 3/22/22 14:01, Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

OK, I have pushed that.

It seems like you could remove the NO_INSTALLCHECK restriction
too. You already removed the comment defending it, and it
seems to work fine as an installcheck now if I remove that
locally.

Other nitpicks:

* the IsParallelWorker test could use a comment

* I notice a typo "permisisons" in test_oat_hooks.sql

Fixed

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#30)
Re: New Object Access Type hooks

Andrew Dunstan <andrew@dunslane.net> writes:

Fixed

Now that we got past the hard failures, we can see that the test
falls over with (some?) non-default encodings, as for instance
here:

https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=prion&amp;dt=2022-03-22%2020%3A23%3A13

I can replicate that by running the test under LANG=en_US.iso885915.
What I think is happening is:

(1) Rather unwisely, the relevant InvokeNamespaceSearchHook calls
appear in recomputeNamespacePath. That means that their timing
depends heavily on accidents of caching.

(2) If we decide that we need an encoding conversion to talk to
the client, there'll be a lookup for the conversion function
early during session startup. That will cause the namespace
search path to get computed then, before the test module has been
loaded and certainly before the audit GUC has been turned on.

(3) At the point where the test expects some audit notices
to come out, nothing happens because the search path is
already validated.

I'm inclined to think that (1) is a seriously bad idea,
not only because of this instability, but because

(a) the namespace cache logic is unlikely to cause the search-path
cache to get invalidated when something happens that might cause an
OAT hook to wish to change its decision, and

(b) this placement means that the hook is invoked during cache loading
operations that are likely to be super-sensitive to any additional
catalog accesses a hook might wish to do. (I await the results of the
CLOBBER_CACHE_ALWAYS animals with trepidation.)

Now, if our attitude to the OAT hooks is that we are going to
sprinkle some at random and whether they are useful is someone
else's problem, then maybe these are not interesting concerns.

regards, tom lane

#32Andres Freund
andres@anarazel.de
In reply to: Andrew Dunstan (#28)
Re: New Object Access Type hooks

Hi,

On 2022-03-22 13:47:27 -0400, Andrew Dunstan wrote:

OK, I have pushed that.

Seems like it might actually be good to test that object access hooks work
well in a parallel worker. How about going the other way and explicitly setting
force_parallel_mode = disabled for parts of the test and to enabled for
others?

- Andres

#33Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Andres Freund (#32)
Re: New Object Access Type hooks

On Mar 22, 2022, at 3:20 PM, Andres Freund <andres@anarazel.de> wrote:

Seems like it might actually be good to test that object access hooks work
well in a parallel worker. How about going the other way and explicitly setting
force_parallel_mode = disabled for parts of the test and to enabled for
others?

Wouldn't we get differing numbers of NOTICE messages depending on how many parallel workers there are? Or would you propose setting the number of workers to a small, fixed value?


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#34Tom Lane
tgl@sss.pgh.pa.us
In reply to: Mark Dilger (#33)
Re: New Object Access Type hooks

Mark Dilger <mark.dilger@enterprisedb.com> writes:

On Mar 22, 2022, at 3:20 PM, Andres Freund <andres@anarazel.de> wrote:
Seems like it might actually be good to test that object access hooks work
well in a parallel worker. How about going the other way and explicitly setting
force_parallel_mode = disabled for parts of the test and to enabled for
others?

Wouldn't we get differing numbers of NOTICE messages depending on how
many parallel workers there are? Or would you propose setting the
number of workers to a small, fixed value?

The value would have to be "1", else you are going to have issues
with notices from different workers being interleaved differently
from run to run. You might have that anyway, due to interleaving
of leader and worker messages.

regards, tom lane

#35Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#34)
Re: New Object Access Type hooks

Hi,

On 2022-03-22 18:41:45 -0400, Tom Lane wrote:

Mark Dilger <mark.dilger@enterprisedb.com> writes:

On Mar 22, 2022, at 3:20 PM, Andres Freund <andres@anarazel.de> wrote:
Seems like it might actually be good to test that object access hooks work
well in a parallel worker. How about going the other way and explicitly setting
force_parallel_mode = disabled for parts of the test and to enabled for
others?

Wouldn't we get differing numbers of NOTICE messages depending on how
many parallel workers there are? Or would you propose setting the
number of workers to a small, fixed value?

Yes.

The value would have to be "1", else you are going to have issues
with notices from different workers being interleaved differently
from run to run.

Yea. Possible one could work around those with some effort (using multiple
notification channels maybe), but there seems little to glean from multiple
workers that a single worker wouldn't show.

You might have that anyway, due to interleaving of leader and worker
messages.

That part could perhaps be addressed by setting parallel_leader_participation
= 0.

Greetings,

Andres Freund

#36Justin Pryzby
pryzby@telsasoft.com
In reply to: Andrew Dunstan (#30)
Re: New Object Access Type hooks

If I'm not wrong, this is still causing issues at least on cfbot/windows, even
since f0206d99.

https://cirrus-ci.com/task/5266352712712192
https://cirrus-ci.com/task/5061218867085312
https://cirrus-ci.com/task/5663822005403648
https://cirrus-ci.com/task/5744257246953472

https://cirrus-ci.com/task/5744257246953472
[22:26:50.939] test test_oat_hooks ... FAILED 173 ms

https://api.cirrus-ci.com/v1/artifact/task/5744257246953472/log/src/test/modules/test_oat_hooks/regression.diffs
diff -w -U3 c:/cirrus/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out c:/cirrus/src/test/modules/test_oat_hooks/results/test_oat_hooks.out
--- c:/cirrus/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out	2022-03-22 22:13:23.386895200 +0000
+++ c:/cirrus/src/test/modules/test_oat_hooks/results/test_oat_hooks.out	2022-03-22 22:26:51.104419600 +0000
@@ -15,12 +15,6 @@
 NOTICE:  in process utility: superuser finished CreateRoleStmt
 CREATE TABLE regress_test_table (t text);
 NOTICE:  in process utility: superuser attempting CreateStmt
-NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
-LINE 1: CREATE TABLE regress_test_table (t text);
-                     ^
-NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
-LINE 1: CREATE TABLE regress_test_table (t text);
-                     ^
 NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
#37Tom Lane
tgl@sss.pgh.pa.us
In reply to: Justin Pryzby (#36)
Re: New Object Access Type hooks

Justin Pryzby <pryzby@telsasoft.com> writes:

If I'm not wrong, this is still causing issues at least on cfbot/windows, even
since f0206d99.

That's probably a variant of the encoding dependency I described
upthread.

regards, tom lane

#38Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#31)
Re: New Object Access Type hooks

On 3/22/22 18:18, Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

Fixed

Now that we got past the hard failures, we can see that the test
falls over with (some?) non-default encodings, as for instance
here:

https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=prion&amp;dt=2022-03-22%2020%3A23%3A13

I can replicate that by running the test under LANG=en_US.iso885915.
What I think is happening is:

(1) Rather unwisely, the relevant InvokeNamespaceSearchHook calls
appear in recomputeNamespacePath. That means that their timing
depends heavily on accidents of caching.

(2) If we decide that we need an encoding conversion to talk to
the client, there'll be a lookup for the conversion function
early during session startup. That will cause the namespace
search path to get computed then, before the test module has been
loaded and certainly before the audit GUC has been turned on.

(3) At the point where the test expects some audit notices
to come out, nothing happens because the search path is
already validated.

I'm inclined to think that (1) is a seriously bad idea,
not only because of this instability, but because

(a) the namespace cache logic is unlikely to cause the search-path
cache to get invalidated when something happens that might cause an
OAT hook to wish to change its decision, and

(b) this placement means that the hook is invoked during cache loading
operations that are likely to be super-sensitive to any additional
catalog accesses a hook might wish to do. (I await the results of the
CLOBBER_CACHE_ALWAYS animals with trepidation.)

Now, if our attitude to the OAT hooks is that we are going to
sprinkle some at random and whether they are useful is someone
else's problem, then maybe these are not interesting concerns.

So this was a pre-existing problem that the test has exposed? I don't
think we can just say "you deal with it", and if I understand you right
you don't think that either.

I could make the buildfarm quiet again by resetting NO_INSTALLCHECK
temporarily.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#39Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#38)
Re: New Object Access Type hooks

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/22/22 18:18, Tom Lane wrote:

Now, if our attitude to the OAT hooks is that we are going to
sprinkle some at random and whether they are useful is someone
else's problem, then maybe these are not interesting concerns.

So this was a pre-existing problem that the test has exposed? I don't
think we can just say "you deal with it", and if I understand you right
you don't think that either.

Yeah, my point exactly: the placement of those hooks needs to be rethought.
I'm guessing what we ought to do is let the cached namespace OID list
get built without interference, and then allow the OAT hook to filter
it when it's read.

I could make the buildfarm quiet again by resetting NO_INSTALLCHECK
temporarily.

I was able to reproduce it under "make check" as long as I had
LANG set to one of the troublesome values, so I'm not real sure
that that'll be enough.

regards, tom lane

#40Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#39)
Re: New Object Access Type hooks

On 3/22/22 20:07, Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/22/22 18:18, Tom Lane wrote:

Now, if our attitude to the OAT hooks is that we are going to
sprinkle some at random and whether they are useful is someone
else's problem, then maybe these are not interesting concerns.

So this was a pre-existing problem that the test has exposed? I don't
think we can just say "you deal with it", and if I understand you right
you don't think that either.

Yeah, my point exactly: the placement of those hooks needs to be rethought.
I'm guessing what we ought to do is let the cached namespace OID list
get built without interference, and then allow the OAT hook to filter
it when it's read.

I could make the buildfarm quiet again by resetting NO_INSTALLCHECK
temporarily.

I was able to reproduce it under "make check" as long as I had
LANG set to one of the troublesome values, so I'm not real sure
that that'll be enough.

The buildfarm only runs installcheck under different locales/encodings.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#41Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#40)
Re: New Object Access Type hooks

On 3/22/22 20:11, Andrew Dunstan wrote:

On 3/22/22 20:07, Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/22/22 18:18, Tom Lane wrote:

Now, if our attitude to the OAT hooks is that we are going to
sprinkle some at random and whether they are useful is someone
else's problem, then maybe these are not interesting concerns.

So this was a pre-existing problem that the test has exposed? I don't
think we can just say "you deal with it", and if I understand you right
you don't think that either.

Yeah, my point exactly: the placement of those hooks needs to be rethought.
I'm guessing what we ought to do is let the cached namespace OID list
get built without interference, and then allow the OAT hook to filter
it when it's read.

I could make the buildfarm quiet again by resetting NO_INSTALLCHECK
temporarily.

I was able to reproduce it under "make check" as long as I had
LANG set to one of the troublesome values, so I'm not real sure
that that'll be enough.

The buildfarm only runs installcheck under different locales/encodings.

But you're right about the non-installcheck cases. fairywren had that
issue. I have committed a (tested) fix for those too to force
NO_LOCALE/UTF8.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#42Andres Freund
andres@anarazel.de
In reply to: Andrew Dunstan (#41)
Re: New Object Access Type hooks

Hi,

I just rebased the meson tree (atop 75b1521dae1) and the test_oat_hooks test
fail on windows with meson. They don't with our "homegrown" buildsystem, but
just because it won't run them.

https://cirrus-ci.com/build/6101947223638016
https://cirrus-ci.com/task/5869668815601664?logs=check_world#L67
https://api.cirrus-ci.com/v1/artifact/task/5869668815601664/log/build/testrun/test_oat_hooks/pg_regress/regression.diffs

diff -w -U3 C:/cirrus/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out C:/cirrus/build/testrun/test_oat_hooks/pg_regress/results/test_oat_hooks.out
--- C:/cirrus/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out	2022-03-24 18:56:39.592048000 +0000
+++ C:/cirrus/build/testrun/test_oat_hooks/pg_regress/results/test_oat_hooks.out	2022-03-24 19:03:33.910466700 +0000
@@ -15,12 +15,6 @@
 NOTICE:  in process utility: superuser finished CreateRoleStmt
 CREATE TABLE regress_test_table (t text);
 NOTICE:  in process utility: superuser attempting CreateStmt
-NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
-LINE 1: CREATE TABLE regress_test_table (t text);
-                     ^
-NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
-LINE 1: CREATE TABLE regress_test_table (t text);
-                     ^
 NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
 NOTICE:  in object access: superuser finished create (subId=0) [explicit]
 NOTICE:  in object access: superuser attempting create (subId=0) [explicit]

I don't think this is meson specific...

Greetings,

Andres Freund

#43Andrew Dunstan
andrew@dunslane.net
In reply to: Andres Freund (#42)
Re: New Object Access Type hooks

On 3/24/22 16:59, Andres Freund wrote:

Hi,

I just rebased the meson tree (atop 75b1521dae1) and the test_oat_hooks test
fail on windows with meson. They don't with our "homegrown" buildsystem, but
just because it won't run them.

https://cirrus-ci.com/build/6101947223638016
https://cirrus-ci.com/task/5869668815601664?logs=check_world#L67
https://api.cirrus-ci.com/v1/artifact/task/5869668815601664/log/build/testrun/test_oat_hooks/pg_regress/regression.diffs

diff -w -U3 C:/cirrus/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out C:/cirrus/build/testrun/test_oat_hooks/pg_regress/results/test_oat_hooks.out
--- C:/cirrus/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out	2022-03-24 18:56:39.592048000 +0000
+++ C:/cirrus/build/testrun/test_oat_hooks/pg_regress/results/test_oat_hooks.out	2022-03-24 19:03:33.910466700 +0000
@@ -15,12 +15,6 @@
NOTICE:  in process utility: superuser finished CreateRoleStmt
CREATE TABLE regress_test_table (t text);
NOTICE:  in process utility: superuser attempting CreateStmt
-NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
-LINE 1: CREATE TABLE regress_test_table (t text);
-                     ^
-NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
-LINE 1: CREATE TABLE regress_test_table (t text);
-                     ^
NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
NOTICE:  in object access: superuser finished create (subId=0) [explicit]
NOTICE:  in object access: superuser attempting create (subId=0) [explicit]

I don't think this is meson specific...

Even if you use NO_LOCALE=1/ENCODING=UTF8 as the Makefile now does?

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#44Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#43)
Re: New Object Access Type hooks

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/24/22 16:59, Andres Freund wrote:

I just rebased the meson tree (atop 75b1521dae1) and the test_oat_hooks test
fail on windows with meson.

Even if you use NO_LOCALE=1/ENCODING=UTF8 as the Makefile now does?

Note that that's basically a workaround for buggy placement of the
OAT hooks, as per previous discussion. I hope that we fix that bug
pretty soon, so it shouldn't really be a factor for the meson conversion.

regards, tom lane

#45Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#44)
Re: New Object Access Type hooks

Hi,

On 2022-03-24 17:44:31 -0400, Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 3/24/22 16:59, Andres Freund wrote:

I just rebased the meson tree (atop 75b1521dae1) and the test_oat_hooks test
fail on windows with meson.

Even if you use NO_LOCALE=1/ENCODING=UTF8 as the Makefile now does?

I didn't do that, no. I guess I should have re-checked that in the makefile /
reread the commit message in more detail - although it really doesn't provide
a lot of that. That's, uh, some shoveling problems under the carpet.

I'll do that for now then.

Note that that's basically a workaround for buggy placement of the
OAT hooks, as per previous discussion. I hope that we fix that bug
pretty soon, so it shouldn't really be a factor for the meson conversion.

Yea. I found it's a lot easier to rebase the meson tree frequently rather than
do it in larger batches, so I just do so every few days...

Greetings,

Andres Freund

#46Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Thomas Munro (#10)
Re: New Object Access Type hooks

On Mar 21, 2022, at 10:03 PM, Thomas Munro <thomas.munro@gmail.com> wrote:

On Fri, Mar 18, 2022 at 4:22 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:

(FYI, I got a test failure from src/test/recovery/t/013_crash_restart.pl when testing v1-0001. I'm not sure yet what that is about.)

Doesn't look like 0001 has anything to do with that... Are you on a
Mac? Did it look like this recent failure from CI?

https://cirrus-ci.com/task/4686108033286144
https://api.cirrus-ci.com/v1/artifact/task/4686108033286144/log/src/test/recovery/tmp_check/log/regress_log_013_crash_restart
https://api.cirrus-ci.com/v1/artifact/task/4686108033286144/log/src/test/recovery/tmp_check/log/013_crash_restart_primary.log

I have no idea what is going on there, but searching for discussion
brought me here...

I just got a crash in this test again. Are you still interested? I still have the logs. No core file appears to have been generated.

The test failure is

not ok 5 - psql query died successfully after SIGQUIT


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#47Tom Lane
tgl@sss.pgh.pa.us
In reply to: Mark Dilger (#46)
Re: New Object Access Type hooks

Mark Dilger <mark.dilger@enterprisedb.com> writes:

I just got a crash in this test again. Are you still interested? I still have the logs. No core file appears to have been generated.
The test failure is
not ok 5 - psql query died successfully after SIGQUIT

Hmm ... I can see one problem with that test:

ok( pump_until(
$killme,
$psql_timeout,
\$killme_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
"psql query died successfully after SIGQUIT");

It's been a little while since that message looked like that.
Nowadays you get

WARNING: terminating connection because of unexpected SIGQUIT signal
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.

Usually the test would succeed anyway because of matching the
second or third regex alternative, but I wonder if there is
some other spelling of libpq's complaint that shows up
occasionally. It'd be nice if we could see the contents of
$killme_stderr upon failure.

regards, tom lane

#48Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#47)
Re: New Object Access Type hooks

I wrote:

Usually the test would succeed anyway because of matching the
second or third regex alternative, but I wonder if there is
some other spelling of libpq's complaint that shows up
occasionally. It'd be nice if we could see the contents of
$killme_stderr upon failure.

OK, now I'm confused, because pump_until is very clearly
*trying* to report exactly that:

if (not $proc->pumpable())
{
diag("pump_until: process terminated unexpectedly when searching for \"$until\" with stream: \"$$stream\"");
return 0;
}

and if I intentionally break the regex then I do see this
output when running the test by hand:

# Running: pg_ctl kill QUIT 1922645
ok 4 - killed process with SIGQUIT
# pump_until: process terminated unexpectedly when searching for "(?^m:WARNING: terminating connection because of crash of another server process|server closed the connection foounexpectedly|connection to server was lost)" with stream: "psql:<stdin>:9: WARNING: terminating connection because of unexpected SIGQUIT signal
# psql:<stdin>:9: server closed the connection unexpectedly
# This probably means the server terminated abnormally
# before or while processing the request.
# "
not ok 5 - psql query died successfully after SIGQUIT

Is our CI setup failing to capture stderr from TAP tests??

regards, tom lane

#49Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Tom Lane (#48)
Re: New Object Access Type hooks

On Apr 4, 2022, at 10:41 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Is our CI setup failing to capture stderr from TAP tests??

I'm looking into the way our TAP test infrastructure assigns port numbers to nodes, and whether that is reasonable during parallel test runs with nodes stopping and starting again. On casual inspection, that doesn't seem ok, because the Cluster.pm logic to make sure nodes get unique ports doesn't seem to be thinking about other parallel tests running. It will notice if another node is already bound to the port, but if another node has been killed and not yet restarted, won't things go off the rails?

I'm writing a parallel test just for this. Will get back to you.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#50Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#48)
Re: New Object Access Type hooks

I wrote:

Is our CI setup failing to capture stderr from TAP tests??

Oh, I'm barking up the wrong tree. This test must have been run
against HEAD between 6da65a3f9 (23 Feb) and 2beb4acff (31 Mar), when
pump_until indeed didn't print this essential information :-(

If you just got this failure, could you look in the log to
see if there's a pump_until report?

regards, tom lane

#51Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Tom Lane (#50)
1 attachment(s)
Re: New Object Access Type hooks

On Apr 4, 2022, at 10:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Oh, I'm barking up the wrong tree. This test must have been run
against HEAD between 6da65a3f9 (23 Feb) and 2beb4acff (31 Mar), when
pump_until indeed didn't print this essential information :-(

If you just got this failure, could you look in the log to
see if there's a pump_until report?

I was running `make -j12 check-world` against my local patched version of master:

commit 80399fa5f208c4acd4ec194c47e534ba8dd3ae7c (HEAD -> 0001)
Author: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Mon Mar 28 13:35:11 2022 -0700

Allow grant and revoke of privileges on parameters

Add new SET and ALTER SYSTEM privileges for configuration parameters
(GUCs), and a new catalog, pg_parameter_acl, for tracking grants of
these privileges.

The privilege to SET a parameter marked USERSET is inherent in that
parameter's marking and cannot be revoked. This saves cycles when
performing SET operations, as looking up privileges in the catalog
can be skipped. If we find that administrators need to revoke SET
privilege on a particular variable from public, that variable can be
redefined in future releases as SUSET with a default grant of SET to
PUBLIC issued.

commit 4eb9798879680dcc0e3ebb301cf6f925dfa69422 (origin/master, origin/HEAD, master)
Author: Andrew Dunstan <andrew@dunslane.net>
Date: Mon Apr 4 10:12:30 2022 -0400

Avoid freeing objects during json aggregate finalization

Commit f4fb45d15c tried to free memory during aggregate finalization.
This cause issues, particularly when used as a window function, so stop
doing that.

Per complaint by Jaime Casanova and diagnosis by Andres Freund

Discussion: /messages/by-id/YkfeMNYRCGhySKyg@ahch-to

The test logs are attached.

Attachments:

013_crash_restart_primary.logapplication/octet-stream; name=013_crash_restart_primary.log; x-unix-mode=0600Download
#52Tom Lane
tgl@sss.pgh.pa.us
In reply to: Mark Dilger (#51)
Re: New Object Access Type hooks

Mark Dilger <mark.dilger@enterprisedb.com> writes:

The test logs are attached.

Server log looks as-expected:

2022-04-04 09:11:51.087 PDT [2084] 013_crash_restart.pl LOG: statement: SELECT pg_sleep(3600);
2022-04-04 09:11:51.094 PDT [2083] 013_crash_restart.pl WARNING: terminating connection because of unexpected SIGQUIT signal
2022-04-04 09:11:51.095 PDT [2070] LOG: server process (PID 2083) exited with exit code 2
2022-04-04 09:11:51.095 PDT [2070] DETAIL: Failed process was running: INSERT INTO alive VALUES($$in-progress-before-sigquit$$) RETURNING status;
2022-04-04 09:11:51.095 PDT [2070] LOG: terminating any other active server processes

I was hoping to see regress_log_013_crash_restart, though.

regards, tom lane

#53Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Tom Lane (#52)
1 attachment(s)
Re: New Object Access Type hooks
Show quoted text

On Apr 4, 2022, at 11:07 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I was hoping to see regress_log_013_crash_restart, though.

Attachments:

regress_log_013_crash_restartapplication/octet-stream; name=regress_log_013_crash_restart; x-unix-mode=0644Download
#54Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Mark Dilger (#49)
Re: New Object Access Type hooks

On Apr 4, 2022, at 10:44 AM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:

I'm writing a parallel test just for this. Will get back to you.

Ok, that experiment didn't accomplish anything, beyond refreshing my memory regarding Cluster.pm preferring sockets over ports.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#55Tom Lane
tgl@sss.pgh.pa.us
In reply to: Mark Dilger (#53)
Re: New Object Access Type hooks

Mark Dilger <mark.dilger@enterprisedb.com> writes:

# Running: pg_ctl kill QUIT 2083
ok 4 - killed process with SIGQUIT
# pump_until: process terminated unexpectedly when searching for "(?^m:WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost)" with stream: "psql:<stdin>:9: WARNING: terminating connection because of unexpected SIGQUIT signal
# psql:<stdin>:9: could not send data to server: Socket is not connected
# "
not ok 5 - psql query died successfully after SIGQUIT

And there we have it: the test wasn't updated for the new backend message
spelling, and we're seeing a different frontend behavior. Evidently the
backend is dying before we're able to send the "SELECT 1;" to it.

I'm not quite sure whether it's a libpq bug that it doesn't produce the
"connection to server was lost" message here, but in any case I suspect
that we shouldn't be checking for the second and third regex alternatives.
The "terminating connection" warning absolutely should get through, and
if it doesn't we want to know about it. So my proposal for a fix is
to change the regex to be just "WARNING: terminating connection because
of unexpected SIGQUIT signal".

regards, tom lane

#56Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#55)
Re: New Object Access Type hooks

I wrote:

The "terminating connection" warning absolutely should get through,

... oh, no, that's not guaranteed at all, since it's sent from quickdie().
So scratch that. Maybe we'd better add "could not send data to server"
to the regex?

regards, tom lane

#57Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Tom Lane (#56)
Re: New Object Access Type hooks

On Apr 4, 2022, at 12:05 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

The "terminating connection" warning absolutely should get through,

... oh, no, that's not guaranteed at all, since it's sent from quickdie().
So scratch that. Maybe we'd better add "could not send data to server"
to the regex?

If it fails in pqsecure_raw_write(), you get either "server closed the connection unexpectedly" or "could not send data to server". Do we need to support pgtls_write() or pg_GSS_write(), which have different error messages? Can anybody run the tests with TLS or GSS enabled? I assume the test framework prevents this, but I didn't check too closely....

Is it possible that pgFlush will call pqSendSome which calls pqReadData before trying to write anything, and get back a "could not receive data from server" from pqsecure_raw_read()?

It's a bit hard to prove to myself which paths might be followed through this code. Thoughts?


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#58Tom Lane
tgl@sss.pgh.pa.us
In reply to: Mark Dilger (#57)
Re: New Object Access Type hooks

Mark Dilger <mark.dilger@enterprisedb.com> writes:

On Apr 4, 2022, at 12:05 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
So scratch that. Maybe we'd better add "could not send data to server"
to the regex?

If it fails in pqsecure_raw_write(), you get either "server closed the connection unexpectedly" or "could not send data to server". Do we need to support pgtls_write() or pg_GSS_write(), which have different error messages?

Don't see why, since this test sets up a new cluster in which neither
is enabled.

Is it possible that pgFlush will call pqSendSome which calls pqReadData before trying to write anything, and get back a "could not receive data from server" from pqsecure_raw_read()?

Yeah, it's plausible to get a failure on either the write or read side
depending on timing.

Perhaps libpq should be trying harder to make those cases look alike, but
this test is about server behavior not libpq behavior, so I'm inclined
to just make it lax.

regards, tom lane

#59Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Tom Lane (#58)
Re: New Object Access Type hooks

On Apr 4, 2022, at 1:47 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Yeah, it's plausible to get a failure on either the write or read side
depending on timing.

Perhaps libpq should be trying harder to make those cases look alike, but
this test is about server behavior not libpq behavior, so I'm inclined
to just make it lax.

+1.

I've gotten this test failure only a few times in perhaps the last six months, so if we narrow the opportunity for test failure without closing it entirely, we're just making the test failures that much harder to diagnose.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#60Tom Lane
tgl@sss.pgh.pa.us
In reply to: Mark Dilger (#59)
Re: New Object Access Type hooks

Mark Dilger <mark.dilger@enterprisedb.com> writes:

On Apr 4, 2022, at 1:47 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Perhaps libpq should be trying harder to make those cases look alike, but
this test is about server behavior not libpq behavior, so I'm inclined
to just make it lax.

+1.

I've gotten this test failure only a few times in perhaps the last six months, so if we narrow the opportunity for test failure without closing it entirely, we're just making the test failures that much harder to diagnose.

Done that way.

regards, tom lane

#61Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#44)
Re: New Object Access Type hooks

On Thu, Mar 24, 2022 at 05:44:31PM -0400, Tom Lane wrote:

Note that that's basically a workaround for buggy placement of the
OAT hooks, as per previous discussion. I hope that we fix that bug
pretty soon, so it shouldn't really be a factor for the meson conversion.

So, this issue is still listed as an open item. What should we do?
From what I get, the caching issues with the namespace lookup hook are
not new to v15, they just get exposed by the new test module
test_oat_hooks/. FWIW, I would vote against moving around hook calls
in back branches as that could cause compatibility problems in
existing code relying on them, but it surely is unstable to keep these
when recomputing the search_path.

A removal from recomputeNamespacePath() implies an addition at the end
of fetch_search_path() and fetch_search_path_array(). Perhaps an
extra one in RangeVarGetCreationNamespace()? The question is how much
of these we want, for example the search hook would be called now even
when doing relation-specific checks like RelationIsVisible() and the
kind.
--
Michael

#62Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#61)
Re: New Object Access Type hooks

On Mon, Apr 18, 2022 at 03:50:11PM +0900, Michael Paquier wrote:

A removal from recomputeNamespacePath() implies an addition at the end
of fetch_search_path() and fetch_search_path_array(). Perhaps an
extra one in RangeVarGetCreationNamespace()? The question is how much
of these we want, for example the search hook would be called now even
when doing relation-specific checks like RelationIsVisible() and the
kind.

I have been playing with this issue, and if we want to minimize the
number of times the list of namespaces in activeSearchPath gets
checked through the search hook, it looks like this is going to
require an additional cached list of namespace OIDs filtered through
InvokeNamespaceSearchHook(). However, it is unclear to me how we can
guarantee that any of the code paths forcing a recomputation of
activeSearchPath are not used for a caching phase, so it looks rather
easy to mess up things and finish with a code path using an unfiltered
activeSearchPath. The set of *IsVisible() routines should be fine, at
least.

At the end, I am not sure that it is a wise time to redesign this
area close to beta2, so I would vote for leaving this issue aside for
now. Another thing that we could do is to tweak the tests and silence
the part around OAT_NAMESPACE_SEARCH, which would increase the coverage
with installcheck, removing the need for ENCODING and NO_LOCALE.
--
Michael