agora inbox for [email protected]  
help / color / mirror / Atom feed
Auditing extension for PostgreSQL (Take 2)
279+ messages / 7 participants
[nested] [flat]

* Auditing extension for PostgreSQL (Take 2)
@ 2015-02-15 02:34  David Steele <[email protected]>
  0 siblings, 2 replies; 279+ messages in thread

From: David Steele @ 2015-02-15 02:34 UTC (permalink / raw)
  To: pgsql-hackers

I've posted a couple of messages over the last few weeks about the work
I've been doing on the pg_audit extension.  The lack of response could
be due to either universal acclaim or complete apathy, but in any case I
think this is a very important topic so I want to give it another try.

I've extensively reworked the code that was originally submitted by
2ndQuandrant.  This is not an indictment of their work, but rather an
attempt to redress concerns that were expressed by members of the
community.  I've used core functions to determine how audit events
should be classified and simplified and tightened the code wherever
possible.  I've removed deparse and event triggers and opted for methods
that rely only on existing hooks.  In my last message I provided
numerous examples of configuration, usage, and output which I hoped
would alleviate concerns of complexity.  I've also written a ton of unit
tests to make sure that the code works as expected.

Auditing has been a concern everywhere I've used or introduced
PostgreSQL.  Over time I've developed a reasonably comprehensive (but
complex) system of triggers, tables and functions to provide auditing
for customers.  While this has addressed most requirements, there are
always questions of auditing aborted transactions, DDL changes,
configurability, etc. which I have been unable to satisfy.

There has been some discussion of whether or not this module needs to be
in contrib.  One reason customers trust contrib is that the modules are
assumed to be held to a higher standard than code found on GitHub.  This
has already been pointed out.  But I believe another important reason is
that they know packages will be made available for a variety of
platforms, and bugs are more likely to be experienced by many users and
not just a few (or one).  For this reason my policy is not to run
custom-compiled code in production.  This is especially true for
something as mission critical as a relational database.

I realize this is not an ideal solution.  Everybody (including me) wants
something that is in core with real policies and more options.  It's
something that I am personally really eager to work on.  But the reality
is that's not going to happen for 9.5 and probably not for 9.6 either.
Meanwhile, I believe the lack of some form of auditing is harming
adoption of PostgreSQL, especially in the financial and government sectors.

The patch I've attached satisfies the requirements that I've had from
customers in the past.  I'm confident that if it gets out into the wild
it will bring all kinds of criticism and comments which will be valuable
in designing a robust in-core solution.

I'm submitting this patch to the Commitfest.  I'll do everything I can
to address the concerns of the community and I'm happy to provide more
examples as needed.  I'm hoping the sgml docs I've provided with the
patch will cover any questions, but of course feedback is always
appreciated.

--
- David Steele
[email protected]

diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..2eee3b9
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,4 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..b3914ac
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1099 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements.
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_prewarm/pg_prewarm.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String contants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String contants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC		    "MISC"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* SELECT */
+	LOG_READ = (1 << 0),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 1),
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 2),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 4),
+
+	/* Function execution */
+	LOG_MISC = (1 << 5),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String contants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * This module collects AuditEvents from various sources (event triggers, and
+ * executor/utility hooks) and passes them to the log_audit_event() function.
+ *
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If an underlying command affects multiple objects multiple
+ * AuditEvents must be created to represent it.
+ */
+typedef struct
+{
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	bool granted;
+} AuditEvent;
+
+/*
+ * Set if a function below log_utility_command() has logged the event - prevents
+ * more than one function from logging when the event could be logged in
+ * multiple places.
+ */
+bool utilityCommandLogged = false;
+AuditEvent utilityAuditEvent;
+
+/*
+ * Returns the oid of the role specified in pgaudit.role.
+ */
+static Oid
+audit_role_oid()
+{
+	HeapTuple roleTup;
+	Oid oid = InvalidOid;
+
+	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(auditRole));
+
+	if (HeapTupleIsValid(roleTup))
+	{
+		oid = HeapTupleGetOid(roleTup);
+		ReleaseSysCache(roleTup);
+	}
+
+	return oid;
+}
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEvent *e)
+{
+	const char *classname;
+
+	/* Check that this event should be logged. */
+	if (!log_check(e, &classname))
+		return;
+
+	/* Log via ereport(). */
+	ereport(LOG,
+			(errmsg("AUDIT: %s,%s,%s,%s,%s,%s",
+					e->granted ? AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+					classname, e->command, e->objectType, e->objectName,
+					e->commandText),
+			 errhidestmt(true)));
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem    *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* Make a copy of the column set */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for DML operations via executor permissions checks.
+ */
+static void
+log_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+		AuditEvent auditEvent;
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		auditEvent.logStmtLevel = LOGSTMT_MOD;
+
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEvent.commandTag = T_InsertStmt;
+			auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEvent.commandTag = T_UpdateStmt;
+			auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEvent.commandTag = T_DeleteStmt;
+			auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEvent.commandTag = T_SelectStmt;
+			auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEvent.commandTag = T_Invalid;
+			auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEvent.objectName = "";
+			auditEvent.objectType = "";
+
+			log_audit_event(&auditEvent);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEvent.objectType = OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEvent.objectType = OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEvent.granted)
+		{
+			log_audit_event(&auditEvent);
+		}
+
+		pfree(auditEvent.objectName);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		utilityAuditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				utilityAuditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+
+		/* Log the event */
+		log_audit_event(&utilityAuditEvent);
+		utilityCommandLogged = true;
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Generate the fully-qualified function name. */
+	utilityAuditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the event */
+	utilityAuditEvent.logStmtLevel = LOGSTMT_ALL;
+	utilityAuditEvent.commandTag = T_DoStmt;
+	utilityAuditEvent.command = COMMAND_EXECUTE;
+	utilityAuditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	utilityAuditEvent.commandText = debug_query_string;
+
+	log_audit_event(&utilityAuditEvent);
+	utilityCommandLogged = true;
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute. */
+		case OAT_FUNCTION_EXECUTE:
+			log_function_execute(objectId);
+			break;
+
+		/* Log create. */
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter. */
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop. */
+		case OAT_DROP:
+		{
+			ObjectAccessDrop *drop = arg;
+
+			if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+				return;
+
+			log_create_alter_drop(classId, objectId);
+		}
+		break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid = audit_role_oid();
+
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_dml(auditOid, rangeTabls);
+
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	/* Create the utility audit event. */
+	utilityCommandLogged = false;
+
+	utilityAuditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+	utilityAuditEvent.commandTag = nodeTag(parsetree);
+	utilityAuditEvent.command = CreateCommandTag(parsetree);
+	utilityAuditEvent.objectName = "";
+	utilityAuditEvent.objectType = "";
+	utilityAuditEvent.commandText = debug_query_string;
+	utilityAuditEvent.granted = false;
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Log the utility command if logging is on, the command has not already
+	 * been logged by another hook, and the transaction is not aborted */
+	if (auditLogBitmap != 0 && !utilityCommandLogged &&
+		!IsAbortedTransactionBlockState())
+	{
+		log_audit_event(&utilityAuditEvent);
+	}
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState())
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pgaudit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("pgaudit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pgaudit.role = "role1"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pgaudit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pgaudit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pgaudit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..0b39082
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pgaudit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..3a89d50
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1220 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pgAudit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE				=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE		=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION		=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION	=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE		=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE			=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET		=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE			=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_INDEX	=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN				=> 'BEGIN',
+	COMMAND_CLOSE				=> 'CLOSE CURSOR',
+	COMMAND_COMMIT				=> 'COMMIT',
+	COMMAND_COPY				=> 'COPY',
+	COMMAND_COPY_TO				=> 'COPY TO',
+	COMMAND_COPY_FROM			=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE	=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION	=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION	=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE		=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX		=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE			=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR		=> 'DECLARE CURSOR',
+	COMMAND_DO					=> 'DO',
+	COMMAND_DISCARD_ALL			=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION		=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE			=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA		=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE		=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS		=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE		=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA			=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE			=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_INDEX	=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TYPE		=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE				=> 'EXECUTE',
+	COMMAND_EXECUTE_READ		=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE		=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION	=> 'EXECUTE FUNCTION',
+	COMMAND_FETCH				=> 'FETCH',
+	COMMAND_GRANT				=> 'GRANT',
+	COMMAND_INSERT				=> 'INSERT',
+	COMMAND_PREPARE				=> 'PREPARE',
+	COMMAND_PREPARE_READ		=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE		=> 'PREPARE WRITE',
+	COMMAND_REVOKE				=> 'REVOKE',
+	COMMAND_SELECT				=> 'SELECT',
+	COMMAND_SET					=> 'SET',
+	COMMAND_UPDATE				=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE			=> 'TYPE',
+	TYPE_NONE		=> '',
+
+	TYPE_FUNCTION	=> 'FUNCTION',
+	TYPE_INDEX		=> 'INDEX',
+	TYPE_TABLE		=> 'TABLE',
+	TYPE_TYPE		=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pgaudit.log GUCS
+my %oAuditGrantHash;			# Hash to store pgaudit grants
+
+my $strCurrentAuditLog;		# pgaudit.log setting that Postgres was started with
+my $strTemporaryAuditLog;	# pgaudit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+	               " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		PgExecuteOnly($strSql);
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = $oData;
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = ${$oTableHash}{&NAME};
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   # " -c log_destination='stderr,csvlog'" .
+				   # " -c logging_collector=on" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pgaudit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pgaudit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pgaudit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pgaudit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pgaudit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "grant " . lc(${strPrivilege}) .
+								(defined($strColumn) ? " (${strColumn})" : '') .
+								" on ${strObject} to ${strRole}");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "revoke " . lc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " on ${strObject} from ${strRole}");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable; # Store table info for select, insert, update, delete
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+PgLogExecute(COMMAND_CREATE_TABLE, 'create table test (id int)', 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test', 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+             'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+            &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Now create some email friendly tests.  These first tests are session logging
+# only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account (id int, name text, password text,' .
+			 ' description text)', 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pgaudit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+                'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pgaudit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+PgLogExecute(COMMAND_CREATE_SCHEMA, "create schema test");
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+PgLogExecute(COMMAND_CREATE_TABLE_AS,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 'test.pg_class', true, false);
+PgLogExecute(COMMAND_INSERT,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 undef, false, true);
+PgLogExecute(COMMAND_INSERT,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, true, false);
+PgLogExecute(COMMAND_COPY_FROM,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+		     'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test.test_insert (id int)', 'test.test_insert');
+PgLogExecute(COMMAND_PREPARE_WRITE,
+			 'PREPARE pgclassstmt (oid) as insert' .
+			 ' into test.test_insert (id) values ($1)');
+PgLogExecute(COMMAND_INSERT,
+			 'EXECUTE pgclassstmt (1)', undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_WRITE,
+			 'EXECUTE pgclassstmt (1)', undef, false, true);
+
+# Create a table with a primary key
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test', true, false);
+PgLogExecute(COMMAND_CREATE_INDEX,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+PgLogExecute(COMMAND_GRANT, 'grant select on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+PgLogExecute(COMMAND_GRANT, 'grant select (name) on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test drop description', 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test rename to test2', 'public.test');
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test2 set schema test', 'public.test2', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE_INDEX, 'alter table test2 set schema test',
+										'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 add description text',
+								  'test.test2');
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 drop description',
+								  'test.test2');
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, 'drop table test.test2',
+									   'test.test_pkey', false, false);
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test.test2',
+								 'test.test2', true, true);
+
+PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION int_add(a int, b int)' .
+									  ' returns int as $$ begin return a + b;' .
+									  ' end $$language plpgsql');
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add');
+
+PgLogExecute(COMMAND_CREATE_AGGREGATE, "CREATE AGGREGATE sum_test (int)" .
+							" (sfunc = int_add, stype = int, initcond = 0)");
+PgLogExecute(COMMAND_ALTER_AGGREGATE,
+			 "ALTER AGGREGATE sum_test (int) rename to sum_test2");
+
+PgLogExecute(COMMAND_CREATE_COLLATION,
+			 "CREATE COLLATION collation_test FROM \"de_DE\"");
+PgLogExecute(COMMAND_ALTER_COLLATION,
+			 "ALTER COLLATION collation_test rename to collation_test2");
+
+PgLogExecute(COMMAND_CREATE_CONVERSION,
+			 "CREATE CONVERSION conversion_test FOR 'SQL_ASCII' TO".
+			 " 'MULE_INTERNAL' FROM ascii_to_mic");
+PgLogExecute(COMMAND_ALTER_CONVERSION,
+			 "ALTER CONVERSION conversion_test rename to conversion_test2");
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..e4f0bdc 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -124,6 +124,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..f3f4ab9
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,316 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pgaudit" xreflabel="pgaudit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pgaudit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pgaudit.log</literal> GUC.
+        There are five classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pgaudit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pgaudit.log = 'all, -misc'
+          </programlisting>
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pgaudit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pgaudit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pgaudit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pgaudit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Author</title>
+
+    <para>
+      David Steele <email>[email protected]</email>
+    </para>
+  </sect2>
+</sect1>


Attachments:

  [text/plain] pg_audit-v1.patch (76.1K, ../../[email protected]/2-pg_audit-v1.patch)
  download | inline diff:
diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..2eee3b9
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,4 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..b3914ac
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1099 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements.
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_prewarm/pg_prewarm.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String contants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String contants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC		    "MISC"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* SELECT */
+	LOG_READ = (1 << 0),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 1),
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 2),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 4),
+
+	/* Function execution */
+	LOG_MISC = (1 << 5),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String contants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * This module collects AuditEvents from various sources (event triggers, and
+ * executor/utility hooks) and passes them to the log_audit_event() function.
+ *
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If an underlying command affects multiple objects multiple
+ * AuditEvents must be created to represent it.
+ */
+typedef struct
+{
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	bool granted;
+} AuditEvent;
+
+/*
+ * Set if a function below log_utility_command() has logged the event - prevents
+ * more than one function from logging when the event could be logged in
+ * multiple places.
+ */
+bool utilityCommandLogged = false;
+AuditEvent utilityAuditEvent;
+
+/*
+ * Returns the oid of the role specified in pgaudit.role.
+ */
+static Oid
+audit_role_oid()
+{
+	HeapTuple roleTup;
+	Oid oid = InvalidOid;
+
+	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(auditRole));
+
+	if (HeapTupleIsValid(roleTup))
+	{
+		oid = HeapTupleGetOid(roleTup);
+		ReleaseSysCache(roleTup);
+	}
+
+	return oid;
+}
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEvent *e)
+{
+	const char *classname;
+
+	/* Check that this event should be logged. */
+	if (!log_check(e, &classname))
+		return;
+
+	/* Log via ereport(). */
+	ereport(LOG,
+			(errmsg("AUDIT: %s,%s,%s,%s,%s,%s",
+					e->granted ? AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+					classname, e->command, e->objectType, e->objectName,
+					e->commandText),
+			 errhidestmt(true)));
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem    *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* Make a copy of the column set */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for DML operations via executor permissions checks.
+ */
+static void
+log_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+		AuditEvent auditEvent;
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		auditEvent.logStmtLevel = LOGSTMT_MOD;
+
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEvent.commandTag = T_InsertStmt;
+			auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEvent.commandTag = T_UpdateStmt;
+			auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEvent.commandTag = T_DeleteStmt;
+			auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEvent.commandTag = T_SelectStmt;
+			auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEvent.commandTag = T_Invalid;
+			auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEvent.objectName = "";
+			auditEvent.objectType = "";
+
+			log_audit_event(&auditEvent);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEvent.objectType = OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEvent.objectType = OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEvent.granted)
+		{
+			log_audit_event(&auditEvent);
+		}
+
+		pfree(auditEvent.objectName);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		utilityAuditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				utilityAuditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+
+		/* Log the event */
+		log_audit_event(&utilityAuditEvent);
+		utilityCommandLogged = true;
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Generate the fully-qualified function name. */
+	utilityAuditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the event */
+	utilityAuditEvent.logStmtLevel = LOGSTMT_ALL;
+	utilityAuditEvent.commandTag = T_DoStmt;
+	utilityAuditEvent.command = COMMAND_EXECUTE;
+	utilityAuditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	utilityAuditEvent.commandText = debug_query_string;
+
+	log_audit_event(&utilityAuditEvent);
+	utilityCommandLogged = true;
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute. */
+		case OAT_FUNCTION_EXECUTE:
+			log_function_execute(objectId);
+			break;
+
+		/* Log create. */
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter. */
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop. */
+		case OAT_DROP:
+		{
+			ObjectAccessDrop *drop = arg;
+
+			if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+				return;
+
+			log_create_alter_drop(classId, objectId);
+		}
+		break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid = audit_role_oid();
+
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_dml(auditOid, rangeTabls);
+
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	/* Create the utility audit event. */
+	utilityCommandLogged = false;
+
+	utilityAuditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+	utilityAuditEvent.commandTag = nodeTag(parsetree);
+	utilityAuditEvent.command = CreateCommandTag(parsetree);
+	utilityAuditEvent.objectName = "";
+	utilityAuditEvent.objectType = "";
+	utilityAuditEvent.commandText = debug_query_string;
+	utilityAuditEvent.granted = false;
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Log the utility command if logging is on, the command has not already
+	 * been logged by another hook, and the transaction is not aborted */
+	if (auditLogBitmap != 0 && !utilityCommandLogged &&
+		!IsAbortedTransactionBlockState())
+	{
+		log_audit_event(&utilityAuditEvent);
+	}
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState())
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pgaudit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("pgaudit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pgaudit.role = "role1"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pgaudit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pgaudit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pgaudit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..0b39082
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pgaudit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..3a89d50
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1220 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pgAudit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE				=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE		=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION		=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION	=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE		=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE			=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET		=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE			=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_INDEX	=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN				=> 'BEGIN',
+	COMMAND_CLOSE				=> 'CLOSE CURSOR',
+	COMMAND_COMMIT				=> 'COMMIT',
+	COMMAND_COPY				=> 'COPY',
+	COMMAND_COPY_TO				=> 'COPY TO',
+	COMMAND_COPY_FROM			=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE	=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION	=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION	=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE		=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX		=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE			=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR		=> 'DECLARE CURSOR',
+	COMMAND_DO					=> 'DO',
+	COMMAND_DISCARD_ALL			=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION		=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE			=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA		=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE		=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS		=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE		=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA			=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE			=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_INDEX	=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TYPE		=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE				=> 'EXECUTE',
+	COMMAND_EXECUTE_READ		=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE		=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION	=> 'EXECUTE FUNCTION',
+	COMMAND_FETCH				=> 'FETCH',
+	COMMAND_GRANT				=> 'GRANT',
+	COMMAND_INSERT				=> 'INSERT',
+	COMMAND_PREPARE				=> 'PREPARE',
+	COMMAND_PREPARE_READ		=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE		=> 'PREPARE WRITE',
+	COMMAND_REVOKE				=> 'REVOKE',
+	COMMAND_SELECT				=> 'SELECT',
+	COMMAND_SET					=> 'SET',
+	COMMAND_UPDATE				=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE			=> 'TYPE',
+	TYPE_NONE		=> '',
+
+	TYPE_FUNCTION	=> 'FUNCTION',
+	TYPE_INDEX		=> 'INDEX',
+	TYPE_TABLE		=> 'TABLE',
+	TYPE_TYPE		=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pgaudit.log GUCS
+my %oAuditGrantHash;			# Hash to store pgaudit grants
+
+my $strCurrentAuditLog;		# pgaudit.log setting that Postgres was started with
+my $strTemporaryAuditLog;	# pgaudit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+	               " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		PgExecuteOnly($strSql);
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = $oData;
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = ${$oTableHash}{&NAME};
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   # " -c log_destination='stderr,csvlog'" .
+				   # " -c logging_collector=on" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pgaudit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pgaudit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pgaudit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pgaudit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pgaudit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "grant " . lc(${strPrivilege}) .
+								(defined($strColumn) ? " (${strColumn})" : '') .
+								" on ${strObject} to ${strRole}");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "revoke " . lc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " on ${strObject} from ${strRole}");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable; # Store table info for select, insert, update, delete
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+PgLogExecute(COMMAND_CREATE_TABLE, 'create table test (id int)', 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test', 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+             'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+            &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Now create some email friendly tests.  These first tests are session logging
+# only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account (id int, name text, password text,' .
+			 ' description text)', 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pgaudit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+                'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pgaudit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+PgLogExecute(COMMAND_CREATE_SCHEMA, "create schema test");
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+PgLogExecute(COMMAND_CREATE_TABLE_AS,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 'test.pg_class', true, false);
+PgLogExecute(COMMAND_INSERT,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 undef, false, true);
+PgLogExecute(COMMAND_INSERT,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, true, false);
+PgLogExecute(COMMAND_COPY_FROM,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+		     'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test.test_insert (id int)', 'test.test_insert');
+PgLogExecute(COMMAND_PREPARE_WRITE,
+			 'PREPARE pgclassstmt (oid) as insert' .
+			 ' into test.test_insert (id) values ($1)');
+PgLogExecute(COMMAND_INSERT,
+			 'EXECUTE pgclassstmt (1)', undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_WRITE,
+			 'EXECUTE pgclassstmt (1)', undef, false, true);
+
+# Create a table with a primary key
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test', true, false);
+PgLogExecute(COMMAND_CREATE_INDEX,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+PgLogExecute(COMMAND_GRANT, 'grant select on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+PgLogExecute(COMMAND_GRANT, 'grant select (name) on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test drop description', 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test rename to test2', 'public.test');
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test2 set schema test', 'public.test2', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE_INDEX, 'alter table test2 set schema test',
+										'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 add description text',
+								  'test.test2');
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 drop description',
+								  'test.test2');
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, 'drop table test.test2',
+									   'test.test_pkey', false, false);
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test.test2',
+								 'test.test2', true, true);
+
+PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION int_add(a int, b int)' .
+									  ' returns int as $$ begin return a + b;' .
+									  ' end $$language plpgsql');
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add');
+
+PgLogExecute(COMMAND_CREATE_AGGREGATE, "CREATE AGGREGATE sum_test (int)" .
+							" (sfunc = int_add, stype = int, initcond = 0)");
+PgLogExecute(COMMAND_ALTER_AGGREGATE,
+			 "ALTER AGGREGATE sum_test (int) rename to sum_test2");
+
+PgLogExecute(COMMAND_CREATE_COLLATION,
+			 "CREATE COLLATION collation_test FROM \"de_DE\"");
+PgLogExecute(COMMAND_ALTER_COLLATION,
+			 "ALTER COLLATION collation_test rename to collation_test2");
+
+PgLogExecute(COMMAND_CREATE_CONVERSION,
+			 "CREATE CONVERSION conversion_test FOR 'SQL_ASCII' TO".
+			 " 'MULE_INTERNAL' FROM ascii_to_mic");
+PgLogExecute(COMMAND_ALTER_CONVERSION,
+			 "ALTER CONVERSION conversion_test rename to conversion_test2");
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..e4f0bdc 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -124,6 +124,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..f3f4ab9
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,316 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pgaudit" xreflabel="pgaudit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pgaudit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pgaudit.log</literal> GUC.
+        There are five classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pgaudit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pgaudit.log = 'all, -misc'
+          </programlisting>
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pgaudit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pgaudit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pgaudit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pgaudit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Author</title>
+
+    <para>
+      David Steele <email>[email protected]</email>
+    </para>
+  </sect2>
+</sect1>


  [application/pgp-signature] signature.asc (819B, ../../[email protected]/3-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-02-17 15:34  Stephen Frost <[email protected]>
  parent: David Steele <[email protected]>
  1 sibling, 1 reply; 279+ messages in thread

From: Stephen Frost @ 2015-02-17 15:34 UTC (permalink / raw)
  To: David Steele <[email protected]>; +Cc: pgsql-hackers; Abhijit Menon-Sen <[email protected]>

David,

I've CC'd Abhijit, the original author of pgaudit, as it seems likely
he'd also be interested in this.

* David Steele ([email protected]) wrote:
> I've posted a couple of messages over the last few weeks about the work
> I've been doing on the pg_audit extension.  The lack of response could
> be due to either universal acclaim or complete apathy, but in any case I
> think this is a very important topic so I want to give it another try.

Thanks!  It's certainly an important topic to a lot of folks; I imagine
the lack of response is simply because people are busy.

> I've extensively reworked the code that was originally submitted by
> 2ndQuandrant.  This is not an indictment of their work, but rather an
> attempt to redress concerns that were expressed by members of the
> community.  I've used core functions to determine how audit events
> should be classified and simplified and tightened the code wherever
> possible.  I've removed deparse and event triggers and opted for methods
> that rely only on existing hooks.  In my last message I provided
> numerous examples of configuration, usage, and output which I hoped
> would alleviate concerns of complexity.  I've also written a ton of unit
> tests to make sure that the code works as expected.

The configuration approach you posted is definitely in-line with what I
was trying to get at previously- thanks for putting some actual code
behind it!  While not a big fan of that other big RDBMS, I do like that
this approach ends up being so similar in syntax.

> I realize this is not an ideal solution.  Everybody (including me) wants
> something that is in core with real policies and more options.  It's
> something that I am personally really eager to work on.  But the reality
> is that's not going to happen for 9.5 and probably not for 9.6 either.
> Meanwhile, I believe the lack of some form of auditing is harming
> adoption of PostgreSQL, especially in the financial and government sectors.

Agreed.

> The patch I've attached satisfies the requirements that I've had from
> customers in the past.  I'm confident that if it gets out into the wild
> it will bring all kinds of criticism and comments which will be valuable
> in designing a robust in-core solution.

This is definitely something that makes sense to me, particularly for
such an important piece.  I had argued previously that a contrib based
solution would make it difficult to build an in-core solution, but
others convinced me that it'd not only be possible but would probably be
preferrable as we'd gain experience with the contrib module and, as you
say, we'd be able to build a better in-core solution based on that
experience.

> I'm submitting this patch to the Commitfest.  I'll do everything I can
> to address the concerns of the community and I'm happy to provide more
> examples as needed.  I'm hoping the sgml docs I've provided with the
> patch will cover any questions, but of course feedback is always
> appreciated.

Glad you submitted it to the CommitFest.  Just glancing through the
code, it certainly looks a lot closer to being something which we could
move forward with.  Using existing functions to work out the categories
(instead of massive switch statements) is certainly much cleaner and
removing those large #ifdef blocks has made the code a lot easier to
follow.

Lastly, I really like all the unit tests..

Additional comments in-line follow.

> diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
> new file mode 100644
> index 0000000..b3914ac
> --- /dev/null
> +++ b/contrib/pg_audit/pg_audit.c
> @@ -0,0 +1,1099 @@
> +/*------------------------------------------------------------------------------
> + * pg_audit.c
> + *
> + * An auditing extension for PostgreSQL. Improves on standard statement logging
> + * by adding more logging classes, object level logging, and providing
> + * fully-qualified object names for all DML and many DDL statements.

It'd be good to quantify what 'many' means above.

> + * Copyright (c) 2014-2015, PostgreSQL Global Development Group
> + *
> + * IDENTIFICATION
> + *		  contrib/pg_prewarm/pg_prewarm.c

Pretty sure this isn't pg_prewarm.c :)

> +/*
> + * String contants for audit types - used when logging to distinguish session
> + * vs. object auditing.
> + */

"String constants"

> +/*
> + * String contants for log classes - used when processing tokens in the
> + * pgaudit.log GUC.
> + */

Ditto.

> +/* String contants for logging commands */

Ditto. :)

> +/*
> + * This module collects AuditEvents from various sources (event triggers, and
> + * executor/utility hooks) and passes them to the log_audit_event() function.

This isn't using event triggers any more, right?  Doesn't look like it.
I don't think that's a problem and it certainly seems to have simplified
things quite a bit, but the comment should be updated.

> +/*
> + * Returns the oid of the role specified in pgaudit.role.
> + */
> +static Oid
> +audit_role_oid()

Couldn't you use get_role_oid() instead of having your own function..?

> +	/*
> +	 * Check privileges granted indirectly via role memberships. We do this in
> +	 * a separate pass to minimize expensive indirect membership tests.  In
> +	 * particular, it's worth testing whether a given ACL entry grants any
> +	 * privileges still of interest before we perform the has_privs_of_role
> +	 * test.
> +	 */

I'm a bit on the fence about this..  Can you provide a use-case where
doing this makes sense..?  Does this mean I could grant admin_role1 to
audit and then get auditing on everything that user1 has access to?
That seems like it might be useful for environments where such roles
already exist though it might end up covering more than is intended..

> +	/* Make a copy of the column set */
> +	tmpSet = bms_copy(attributeSet);

The comment should probably say why, eg:

/* bms_first_member is destructive, so make a copy before using it. */

> +/*
> + * Define GUC variables and install hooks upon module load.
> + */
> +void
> +_PG_init(void)
> +{
> +	if (IsUnderPostmaster)
> +		ereport(ERROR,
> +				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> +				 errmsg("pgaudit must be loaded via shared_preload_libraries")));
> +
> +	/*
> +	 * pgaudit.role = "role1"

I'd make that example "audit", since that's what is generally expected,
right?

> +################################################################################
> +# test.pl - pgAudit Unit Tests
> +################################################################################

This is definitiely nice..

> diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
> new file mode 100644
> index 0000000..f3f4ab9
> --- /dev/null
> +++ b/doc/src/sgml/pgaudit.sgml
> @@ -0,0 +1,316 @@
> +<!-- doc/src/sgml/pgaudit.sgml -->
> +
> +<sect1 id="pgaudit" xreflabel="pgaudit">
> +  <title>pg_audit</title>

There seems to be a number of places which are 'pgaudit' and a bunch
that are 'pg_audit'.  I'm guessing you were thinking 'pg_audit', but
it'd be good to clean up and make them all consistent.

> +  <indexterm zone="pgaudit">
> +    <primary>pg_audit</primary>
> +  </indexterm>
> +
> +  <para>
> +    The <filename>pg_audit</filename> module provides session and object
> +    auditing via the standard logging facility.  Session and object auditing are
> +    completely independent and can be combined.
> +  </para>
> +
> +  <sect2>
> +    <title>Session Auditing</title>
> +
> +    <para>
> +      Session auditing allows the logging of all commands that are executed by
> +      a user in the backend.  Each command is logged with a single entry and
> +      includes the audit type (e.g. <literal>SESSION</literal>), command type
> +      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
> +      statement (e.g. <literal>"select * from test"</literal>).
> +
> +      Fully-qualified names and object types will be logged for
> +      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
> +      <literal>DROP</literal> commands on <literal>TABLE</literal>,
> +      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
> +      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
> +      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
> +      <literal>SEQUENCE</literal> objects as well as function calls.
> +    </para>

Ah, you do have a list of what objects you get fully qualified names
for, nice.  Are there obvious omissions from that list..?  If so, we
might be able to change what happens with objectAccess to include them..

> +      <para>
> +        Enable session logging for all commands except miscellaneous:
> +          <programlisting>
> +pgaudit.log = 'all, -misc'
> +          </programlisting>
> +      </para>
> +    </sect3>

Nice, I like that.

> +    <sect3>
> +      <title>Examples</title>
> +
> +      <para>
> +        Set <literal>pgaudit.log = 'read, ddl'</literal> in
> +        <literal>postgresql.conf</literal>.
> +      </para>

Perhaps I missed it, but it'd be good to point out that GUCs can be set
at various levels.  I know we probably say that somewhere else, but it's
particularly relevant for this.

That was just a quick read.  Would be great if someone else who is
interested would take it for a spin and provide their own feedback.

	Thanks again!

		Stephen


Attachments:

  [application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-02-18 13:25  Simon Riggs <[email protected]>
  parent: David Steele <[email protected]>
  1 sibling, 1 reply; 279+ messages in thread

From: Simon Riggs @ 2015-02-18 13:25 UTC (permalink / raw)
  To: David Steele <[email protected]>; +Cc: pgsql-hackers

On 15 February 2015 at 02:34, David Steele <[email protected]> wrote:

> I've posted a couple of messages over the last few weeks about the work
> I've been doing on the pg_audit extension.  The lack of response could
> be due to either universal acclaim or complete apathy, but in any case I
> think this is a very important topic so I want to give it another try.

You mentioned you had been following the thread for some time and yet
had not contributed to it. Did that indicate your acclaim for the
earlier patch, or was that apathy? I think neither.

People have been working on this feature for >9 months now, so you
having to wait 9 days for a response is neither universal acclaim, nor
apathy. I've waited much longer than that for Stephen to give the
review he promised, but have not bad mouthed him for that wait, nor do
I do so now. In your first post you had removed the author's email
addresses, so they were likely unaware of your post. I certainly was.

> I've extensively reworked the code that was originally submitted by
> 2ndQuandrant.  This is not an indictment of their work, but rather an
> attempt to redress concerns that were expressed by members of the
> community.  I've used core functions to determine how audit events
> should be classified and simplified and tightened the code wherever
> possible.  I've removed deparse and event triggers and opted for methods
> that rely only on existing hooks.  In my last message I provided
> numerous examples of configuration, usage, and output which I hoped
> would alleviate concerns of complexity.  I've also written a ton of unit
> tests to make sure that the code works as expected.

Some people that have contributed ideas to this patch are from
2ndQuadrant, some are not. The main point is that we work together on
things, rather than writing a slightly altered version and then
claiming credit.

If you want to help, please do. We give credit where its due, not to
whoever touched the code last in some kind of bidding war. If we let
this happen, we'd generate a flood of confusing patch versions and
little would ever get committed.

Let's keep to one thread and work to include everybody's ideas then
we'll get something useful committed.

-- 
 Simon Riggs                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, RemoteDBA, Training & Services


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-02-18 15:07  David Steele <[email protected]>
  parent: Simon Riggs <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: David Steele @ 2015-02-18 15:07 UTC (permalink / raw)
  To: Simon Riggs <[email protected]>; +Cc: pgsql-hackers

On 2/18/15 8:25 AM, Simon Riggs wrote:
> On 15 February 2015 at 02:34, David Steele <[email protected]> wrote:
> 
>> I've posted a couple of messages over the last few weeks about the work
>> I've been doing on the pg_audit extension.  The lack of response could
>> be due to either universal acclaim or complete apathy, but in any case I
>> think this is a very important topic so I want to give it another try.
> 
> You mentioned you had been following the thread for some time and yet
> had not contributed to it. Did that indicate your acclaim for the
> earlier patch, or was that apathy? I think neither.

In my case it actually was acclaim.  I was happy with the direction
things were going and had nothing in particular to add - and I didn't
think a +1 from me was going to carry any weight with the community.

I can see now that everyone's opinion matters here, so I'll be more
active about weighing in when I think something is valuable.

> 
> People have been working on this feature for >9 months now, so you
> having to wait 9 days for a response is neither universal acclaim, nor
> apathy. I've waited much longer than that for Stephen to give the
> review he promised, but have not bad mouthed him for that wait, nor do
> I do so now. In your first post you had removed the author's email
> addresses, so they were likely unaware of your post. I certainly was.

I understand that, but with the CF closing I felt like I had to act.
Abhijit's last comment on the thread was that he was no longer going to
work on it in relation to 9.5.  I felt that it was an important feature
(and one that I have a lot of interest in), so that's when I got involved.

I posted two messages, but I only addressed one of them directly to
Abhijit.  As you said, I'm new here and I'm still getting used to the
way things are done.

>> I've extensively reworked the code that was originally submitted by
>> 2ndQuandrant.  This is not an indictment of their work, but rather an
>> attempt to redress concerns that were expressed by members of the
>> community.  I've used core functions to determine how audit events
>> should be classified and simplified and tightened the code wherever
>> possible.  I've removed deparse and event triggers and opted for methods
>> that rely only on existing hooks.  In my last message I provided
>> numerous examples of configuration, usage, and output which I hoped
>> would alleviate concerns of complexity.  I've also written a ton of unit
>> tests to make sure that the code works as expected.
> 
> Some people that have contributed ideas to this patch are from
> 2ndQuadrant, some are not. The main point is that we work together on
> things, rather than writing a slightly altered version and then
> claiming credit.
> 
> If you want to help, please do. We give credit where its due, not to
> whoever touched the code last in some kind of bidding war. If we let
> this happen, we'd generate a flood of confusing patch versions and
> little would ever get committed.

Agreed, and I apologize if I came off that way.  It certainly wasn't my
intention.  I was hesitant because I had made so many changes and I
wasn't sure how the authors would feel about it.  I wrote to them
privately to get their take on the situation.

> Let's keep to one thread and work to include everybody's ideas then
> we'll get something useful committed.

I'm a little confused about how to proceed here.  I created a new thread
because the other patch had already been rejected.  How should I handle
that?

-- 
- David Steele
[email protected]



Attachments:

  [application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-02-23 15:59  David Steele <[email protected]>
  parent: Stephen Frost <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: David Steele @ 2015-02-23 15:59 UTC (permalink / raw)
  To: Stephen Frost <[email protected]>; +Cc: pgsql-hackers; Abhijit Menon-Sen <[email protected]>

Hi Stephen,

Thanks for your review.  All fixed except for comments below:

On 2/17/15 10:34 AM, Stephen Frost wrote:
>> +	/*
>> +	 * Check privileges granted indirectly via role memberships. We do this in
>> +	 * a separate pass to minimize expensive indirect membership tests.  In
>> +	 * particular, it's worth testing whether a given ACL entry grants any
>> +	 * privileges still of interest before we perform the has_privs_of_role
>> +	 * test.
>> +	 */
> 
> I'm a bit on the fence about this..  Can you provide a use-case where
> doing this makes sense..?  Does this mean I could grant admin_role1 to
> audit and then get auditing on everything that user1 has access to?
> That seems like it might be useful for environments where such roles
> already exist though it might end up covering more than is intended..

The idea is that if there are already ready-made roles to be audited
then they don't need to be reconstituted for the audit role.  You could
just do:

grant admin_role to audit;
grant user_role to audit;

Of course, we could list multiple roles in the pg_audit.role GUC, but I
thought this would be easier to use and maintain since there was some
worry about GUCs being fragile when they refer to database objects.

>> +################################################################################
>> +# test.pl - pgAudit Unit Tests
>> +################################################################################
> 
> This is definitiely nice..
> 
>> diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
>> new file mode 100644
>> index 0000000..f3f4ab9
>> --- /dev/null
>> +++ b/doc/src/sgml/pgaudit.sgml
>> @@ -0,0 +1,316 @@
>> +<!-- doc/src/sgml/pgaudit.sgml -->
>> +
>> +<sect1 id="pgaudit" xreflabel="pgaudit">
>> +  <title>pg_audit</title>
> 
> There seems to be a number of places which are 'pgaudit' and a bunch
> that are 'pg_audit'.  I'm guessing you were thinking 'pg_audit', but
> it'd be good to clean up and make them all consistent.

Fixed, though I still left the file name as pgaudit.sgml since all but
one module follow that convention.

>> +    <para>
>> +      Session auditing allows the logging of all commands that are executed by
>> +      a user in the backend.  Each command is logged with a single entry and
>> +      includes the audit type (e.g. <literal>SESSION</literal>), command type
>> +      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
>> +      statement (e.g. <literal>"select * from test"</literal>).
>> +
>> +      Fully-qualified names and object types will be logged for
>> +      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
>> +      <literal>DROP</literal> commands on <literal>TABLE</literal>,
>> +      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
>> +      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
>> +      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
>> +      <literal>SEQUENCE</literal> objects as well as function calls.
>> +    </para>
> 
> Ah, you do have a list of what objects you get fully qualified names
> for, nice.  Are there obvious omissions from that list..?  If so, we
> might be able to change what happens with objectAccess to include them..

It seems like these are the objects where having a name really matters.
 I'm more interested in using the deparse code to handle fully-qualified
names for additional objects rather than adding hooks.

>> +    <sect3>
>> +      <title>Examples</title>
>> +
>> +      <para>
>> +        Set <literal>pgaudit.log = 'read, ddl'</literal> in
>> +        <literal>postgresql.conf</literal>.
>> +      </para>
> 
> Perhaps I missed it, but it'd be good to point out that GUCs can be set
> at various levels.  I know we probably say that somewhere else, but it's
> particularly relevant for this.

Yes, it's very relevant for this patch.  Do you think it's enough to
call out the functionality, or should I provide examples?  Maybe a
separate section just for this concept?

Patch v2 is attached for all changes except the doc change above.

-- 
- David Steele
[email protected]

diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..2eee3b9
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,4 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..ead65a8
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1102 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements (See
+ * pg_audit.sgml for details).
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_audit/pg_audit.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String constants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC		    "MISC"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* SELECT */
+	LOG_READ = (1 << 0),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 1),
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 2),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 4),
+
+	/* Function execution */
+	LOG_MISC = (1 << 5),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String constants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If an underlying command affects multiple objects multiple
+ * AuditEvents must be created to represent it.
+ */
+typedef struct
+{
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	bool granted;
+} AuditEvent;
+
+/*
+ * Set if a function below log_utility_command() has logged the event - prevents
+ * more than one function from logging when the event could be logged in
+ * multiple places.
+ */
+bool utilityCommandLogged = false;
+bool utilityCommandInProgress = false;
+AuditEvent utilityAuditEvent;
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEvent *e)
+{
+	const char *classname;
+
+	/* Check that this event should be logged. */
+	if (!log_check(e, &classname))
+		return;
+
+	/* Log via ereport(). */
+	ereport(LOG,
+			(errmsg("AUDIT: %s,%s,%s,%s,%s,%s",
+					e->granted ? AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+					classname, e->command, e->objectType, e->objectName,
+					e->commandText),
+			 errhidestmt(true)));
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem    *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* bms_first_member is destructive, so make a copy before using it. */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for DML operations via executor permissions checks.
+ */
+static void
+log_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+	AuditEvent auditEvent;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		auditEvent.logStmtLevel = LOGSTMT_MOD;
+
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEvent.commandTag = T_InsertStmt;
+			auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEvent.commandTag = T_UpdateStmt;
+			auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEvent.commandTag = T_DeleteStmt;
+			auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEvent.commandTag = T_SelectStmt;
+			auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEvent.commandTag = T_Invalid;
+			auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEvent.objectName = "";
+			auditEvent.objectType = "";
+
+			log_audit_event(&auditEvent);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEvent.objectType = OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEvent.objectType = OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEvent.granted)
+		{
+			log_audit_event(&auditEvent);
+		}
+
+		pfree(auditEvent.objectName);
+	}
+
+	/*
+	 * If the first flag was never set to false, then rangeTabls was empty. In
+	 * this case log a session select statement.
+	 */
+	if (first && !utilityCommandInProgress)
+	{
+		auditEvent.logStmtLevel = LOGSTMT_ALL;
+		auditEvent.commandTag = T_SelectStmt;
+		auditEvent.command = COMMAND_SELECT;
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+		auditEvent.objectName = "";
+		auditEvent.objectType = "";
+
+		log_audit_event(&auditEvent);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		utilityAuditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				utilityAuditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+
+		/* Log the event */
+		log_audit_event(&utilityAuditEvent);
+		utilityCommandLogged = true;
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Generate the fully-qualified function name. */
+	utilityAuditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the event */
+	utilityAuditEvent.logStmtLevel = LOGSTMT_ALL;
+	utilityAuditEvent.commandTag = T_DoStmt;
+	utilityAuditEvent.command = COMMAND_EXECUTE;
+	utilityAuditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	utilityAuditEvent.commandText = debug_query_string;
+
+	log_audit_event(&utilityAuditEvent);
+	utilityCommandLogged = true;
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute. */
+		case OAT_FUNCTION_EXECUTE:
+			log_function_execute(objectId);
+			break;
+
+		/* Log create. */
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter. */
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop. */
+		case OAT_DROP:
+		{
+			ObjectAccessDrop *drop = arg;
+
+			if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+				return;
+
+			log_create_alter_drop(classId, objectId);
+		}
+		break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid;
+
+	/* Get the audit oid if the role exists. */
+	auditOid = get_role_oid(auditRole, true);
+
+	/* Log DML if the audit role is valid or session logging is enabled. */
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_dml(auditOid, rangeTabls);
+
+	/* Call the next hook function. */
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	/* Create the utility audit event. */
+	utilityCommandLogged = false;
+	utilityCommandInProgress = true;
+
+	utilityAuditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+	utilityAuditEvent.commandTag = nodeTag(parsetree);
+	utilityAuditEvent.command = CreateCommandTag(parsetree);
+	utilityAuditEvent.objectName = "";
+	utilityAuditEvent.objectType = "";
+	utilityAuditEvent.commandText = debug_query_string;
+	utilityAuditEvent.granted = false;
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Log the utility command if logging is on, the command has not already
+	 * been logged by another hook, and the transaction is not aborted */
+	if (auditLogBitmap != 0 && !utilityCommandLogged &&
+		!IsAbortedTransactionBlockState())
+	{
+		log_audit_event(&utilityAuditEvent);
+	}
+
+	utilityCommandInProgress = false;
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState())
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pg_audit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("pg_audit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pg_audit.role = "audit"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pg_audit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pg_audit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pg_audit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..0b39082
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pgaudit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..dcb9fda
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1243 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pg_audit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE				=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE		=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION		=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION	=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE		=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE			=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET		=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE			=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_INDEX	=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN				=> 'BEGIN',
+	COMMAND_CLOSE				=> 'CLOSE CURSOR',
+	COMMAND_COMMIT				=> 'COMMIT',
+	COMMAND_COPY				=> 'COPY',
+	COMMAND_COPY_TO				=> 'COPY TO',
+	COMMAND_COPY_FROM			=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE	=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION	=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION	=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE		=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX		=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE			=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR		=> 'DECLARE CURSOR',
+	COMMAND_DO					=> 'DO',
+	COMMAND_DISCARD_ALL			=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION		=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE			=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA		=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE		=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS		=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE		=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA			=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE			=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_INDEX	=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TYPE		=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE				=> 'EXECUTE',
+	COMMAND_EXECUTE_READ		=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE		=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION	=> 'EXECUTE FUNCTION',
+	COMMAND_EXPLAIN				=> 'EXPLAIN',
+	COMMAND_FETCH				=> 'FETCH',
+	COMMAND_GRANT				=> 'GRANT',
+	COMMAND_INSERT				=> 'INSERT',
+	COMMAND_PREPARE				=> 'PREPARE',
+	COMMAND_PREPARE_READ		=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE		=> 'PREPARE WRITE',
+	COMMAND_REVOKE				=> 'REVOKE',
+	COMMAND_SELECT				=> 'SELECT',
+	COMMAND_SET					=> 'SET',
+	COMMAND_UPDATE				=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE			=> 'TYPE',
+	TYPE_NONE		=> '',
+
+	TYPE_FUNCTION	=> 'FUNCTION',
+	TYPE_INDEX		=> 'INDEX',
+	TYPE_TABLE		=> 'TABLE',
+	TYPE_TYPE		=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pg_audit.log GUCS
+my %oAuditGrantHash;			# Hash to store pg_audit grants
+
+my $strCurrentAuditLog;		# pg_audit.log setting was Postgres was started with
+my $strTemporaryAuditLog;	# pg_audit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXPLAIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+	               " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		PgExecuteOnly($strSql);
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = $oData;
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = ${$oTableHash}{&NAME};
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   # " -c log_destination='stderr,csvlog'" .
+				   # " -c logging_collector=on" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pg_audit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pg_audit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pg_audit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "grant " . lc(${strPrivilege}) .
+								(defined($strColumn) ? " (${strColumn})" : '') .
+								" on ${strObject} to ${strRole}");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "revoke " . lc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " on ${strObject} from ${strRole}");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable; # Store table info for select, insert, update, delete
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+PgLogExecute(COMMAND_CREATE_TABLE, 'create table test (id int)', 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test', 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+             'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+            &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Now create some email friendly tests.  These first tests are session logging
+# only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+
+# !!! Trying to build test to exclude function calls (did not work)
+# PgLogExecute(COMMAND_CREATE_ROLE, 'create role func_owner');
+# PgAuditLogSet(CONTEXT_ROLE, 'func_owner');
+# PgLogExecute(COMMAND_SET, 'set role func_owner');
+# PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION func_test(a int)' .
+# 									  ' returns int as $$ begin return a + 1;' .
+# 									  ' end $$language plpgsql security definer');
+# PgLogExecute(COMMAND_GRANT, 'grant execute on function func_test(int) to user1');
+
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account (id int, name text, password text,' .
+			 ' description text)', 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Test function without auditing (did not work)
+# PgLogExecute(COMMAND_SELECT, 'select func_test(1)');
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pg_audit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+                'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pg_audit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+PgLogExecute(COMMAND_CREATE_SCHEMA, "create schema test");
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+PgLogExecute(COMMAND_CREATE_TABLE_AS,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 'test.pg_class', true, false);
+PgLogExecute(COMMAND_INSERT,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 undef, false, true);
+PgLogExecute(COMMAND_INSERT,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, true, false);
+PgLogExecute(COMMAND_COPY_FROM,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+		     'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test.test_insert (id int)', 'test.test_insert');
+PgLogExecute(COMMAND_PREPARE_WRITE,
+			 'PREPARE pgclassstmt (oid) as insert' .
+			 ' into test.test_insert (id) values ($1)');
+PgLogExecute(COMMAND_INSERT,
+			 'EXECUTE pgclassstmt (1)', undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_WRITE,
+			 'EXECUTE pgclassstmt (1)', undef, false, true);
+
+# Create a table with a primary key
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test', true, false);
+PgLogExecute(COMMAND_CREATE_INDEX,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+PgLogExecute(COMMAND_GRANT, 'grant select on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+PgLogExecute(COMMAND_GRANT, 'grant select (name) on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Try a select that does not reference any tables
+PgLogExecute(COMMAND_SELECT, 'select 1, current_timestamp');
+
+# Try explain
+PgLogExecute(COMMAND_EXPLAIN, 'explain select 1');
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test drop description', 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test rename to test2', 'public.test');
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test2 set schema test', 'public.test2', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE_INDEX, 'alter table test2 set schema test',
+										'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 add description text',
+								  'test.test2');
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 drop description',
+								  'test.test2');
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, 'drop table test.test2',
+									   'test.test_pkey', false, false);
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test.test2',
+								 'test.test2', true, true);
+
+PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION int_add(a int, b int)' .
+									  ' returns int as $$ begin return a + b;' .
+									  ' end $$language plpgsql');
+PgLogExecute(COMMAND_SELECT, "select int_add(1, 1)",
+							 undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add', false, true);
+
+PgLogExecute(COMMAND_CREATE_AGGREGATE, "CREATE AGGREGATE sum_test (int)" .
+							" (sfunc = int_add, stype = int, initcond = 0)");
+PgLogExecute(COMMAND_ALTER_AGGREGATE,
+			 "ALTER AGGREGATE sum_test (int) rename to sum_test2");
+
+PgLogExecute(COMMAND_CREATE_COLLATION,
+			 "CREATE COLLATION collation_test FROM \"de_DE\"");
+PgLogExecute(COMMAND_ALTER_COLLATION,
+			 "ALTER COLLATION collation_test rename to collation_test2");
+
+PgLogExecute(COMMAND_CREATE_CONVERSION,
+			 "CREATE CONVERSION conversion_test FOR 'SQL_ASCII' TO".
+			 " 'MULE_INTERNAL' FROM ascii_to_mic");
+PgLogExecute(COMMAND_ALTER_CONVERSION,
+			 "ALTER CONVERSION conversion_test rename to conversion_test2");
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..e4f0bdc 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -124,6 +124,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..78565de
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,316 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pg_audit" xreflabel="pg_audit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pg_audit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pg_audit.log</literal>
+        GUC. There are five classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pg_audit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pg_audit.log = 'all, -misc'
+          </programlisting>
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pg_audit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pg_audit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Author</title>
+
+    <para>
+      David Steele <email>[email protected]</email>
+    </para>
+  </sect2>
+</sect1>


Attachments:

  [text/plain] pg_audit-v2.patch (77.5K, ../../[email protected]/2-pg_audit-v2.patch)
  download | inline diff:
diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..2eee3b9
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,4 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..ead65a8
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1102 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements (See
+ * pg_audit.sgml for details).
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_audit/pg_audit.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String constants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC		    "MISC"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* SELECT */
+	LOG_READ = (1 << 0),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 1),
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 2),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 4),
+
+	/* Function execution */
+	LOG_MISC = (1 << 5),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String constants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If an underlying command affects multiple objects multiple
+ * AuditEvents must be created to represent it.
+ */
+typedef struct
+{
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	bool granted;
+} AuditEvent;
+
+/*
+ * Set if a function below log_utility_command() has logged the event - prevents
+ * more than one function from logging when the event could be logged in
+ * multiple places.
+ */
+bool utilityCommandLogged = false;
+bool utilityCommandInProgress = false;
+AuditEvent utilityAuditEvent;
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEvent *e)
+{
+	const char *classname;
+
+	/* Check that this event should be logged. */
+	if (!log_check(e, &classname))
+		return;
+
+	/* Log via ereport(). */
+	ereport(LOG,
+			(errmsg("AUDIT: %s,%s,%s,%s,%s,%s",
+					e->granted ? AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+					classname, e->command, e->objectType, e->objectName,
+					e->commandText),
+			 errhidestmt(true)));
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem    *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* bms_first_member is destructive, so make a copy before using it. */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for DML operations via executor permissions checks.
+ */
+static void
+log_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+	AuditEvent auditEvent;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		auditEvent.logStmtLevel = LOGSTMT_MOD;
+
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEvent.commandTag = T_InsertStmt;
+			auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEvent.commandTag = T_UpdateStmt;
+			auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEvent.commandTag = T_DeleteStmt;
+			auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEvent.commandTag = T_SelectStmt;
+			auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEvent.commandTag = T_Invalid;
+			auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEvent.objectName = "";
+			auditEvent.objectType = "";
+
+			log_audit_event(&auditEvent);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEvent.objectType = OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEvent.objectType = OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEvent.granted)
+		{
+			log_audit_event(&auditEvent);
+		}
+
+		pfree(auditEvent.objectName);
+	}
+
+	/*
+	 * If the first flag was never set to false, then rangeTabls was empty. In
+	 * this case log a session select statement.
+	 */
+	if (first && !utilityCommandInProgress)
+	{
+		auditEvent.logStmtLevel = LOGSTMT_ALL;
+		auditEvent.commandTag = T_SelectStmt;
+		auditEvent.command = COMMAND_SELECT;
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+		auditEvent.objectName = "";
+		auditEvent.objectType = "";
+
+		log_audit_event(&auditEvent);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		utilityAuditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				utilityAuditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+
+		/* Log the event */
+		log_audit_event(&utilityAuditEvent);
+		utilityCommandLogged = true;
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Generate the fully-qualified function name. */
+	utilityAuditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the event */
+	utilityAuditEvent.logStmtLevel = LOGSTMT_ALL;
+	utilityAuditEvent.commandTag = T_DoStmt;
+	utilityAuditEvent.command = COMMAND_EXECUTE;
+	utilityAuditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	utilityAuditEvent.commandText = debug_query_string;
+
+	log_audit_event(&utilityAuditEvent);
+	utilityCommandLogged = true;
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute. */
+		case OAT_FUNCTION_EXECUTE:
+			log_function_execute(objectId);
+			break;
+
+		/* Log create. */
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter. */
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop. */
+		case OAT_DROP:
+		{
+			ObjectAccessDrop *drop = arg;
+
+			if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+				return;
+
+			log_create_alter_drop(classId, objectId);
+		}
+		break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid;
+
+	/* Get the audit oid if the role exists. */
+	auditOid = get_role_oid(auditRole, true);
+
+	/* Log DML if the audit role is valid or session logging is enabled. */
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_dml(auditOid, rangeTabls);
+
+	/* Call the next hook function. */
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	/* Create the utility audit event. */
+	utilityCommandLogged = false;
+	utilityCommandInProgress = true;
+
+	utilityAuditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+	utilityAuditEvent.commandTag = nodeTag(parsetree);
+	utilityAuditEvent.command = CreateCommandTag(parsetree);
+	utilityAuditEvent.objectName = "";
+	utilityAuditEvent.objectType = "";
+	utilityAuditEvent.commandText = debug_query_string;
+	utilityAuditEvent.granted = false;
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Log the utility command if logging is on, the command has not already
+	 * been logged by another hook, and the transaction is not aborted */
+	if (auditLogBitmap != 0 && !utilityCommandLogged &&
+		!IsAbortedTransactionBlockState())
+	{
+		log_audit_event(&utilityAuditEvent);
+	}
+
+	utilityCommandInProgress = false;
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState())
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pg_audit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("pg_audit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pg_audit.role = "audit"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pg_audit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pg_audit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pg_audit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..0b39082
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pgaudit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..dcb9fda
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1243 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pg_audit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE				=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE		=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION		=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION	=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE		=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE			=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET		=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE			=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_INDEX	=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN				=> 'BEGIN',
+	COMMAND_CLOSE				=> 'CLOSE CURSOR',
+	COMMAND_COMMIT				=> 'COMMIT',
+	COMMAND_COPY				=> 'COPY',
+	COMMAND_COPY_TO				=> 'COPY TO',
+	COMMAND_COPY_FROM			=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE	=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION	=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION	=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE		=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX		=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE			=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR		=> 'DECLARE CURSOR',
+	COMMAND_DO					=> 'DO',
+	COMMAND_DISCARD_ALL			=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION		=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE			=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA		=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE		=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS		=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE		=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA			=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE			=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_INDEX	=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TYPE		=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE				=> 'EXECUTE',
+	COMMAND_EXECUTE_READ		=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE		=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION	=> 'EXECUTE FUNCTION',
+	COMMAND_EXPLAIN				=> 'EXPLAIN',
+	COMMAND_FETCH				=> 'FETCH',
+	COMMAND_GRANT				=> 'GRANT',
+	COMMAND_INSERT				=> 'INSERT',
+	COMMAND_PREPARE				=> 'PREPARE',
+	COMMAND_PREPARE_READ		=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE		=> 'PREPARE WRITE',
+	COMMAND_REVOKE				=> 'REVOKE',
+	COMMAND_SELECT				=> 'SELECT',
+	COMMAND_SET					=> 'SET',
+	COMMAND_UPDATE				=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE			=> 'TYPE',
+	TYPE_NONE		=> '',
+
+	TYPE_FUNCTION	=> 'FUNCTION',
+	TYPE_INDEX		=> 'INDEX',
+	TYPE_TABLE		=> 'TABLE',
+	TYPE_TYPE		=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pg_audit.log GUCS
+my %oAuditGrantHash;			# Hash to store pg_audit grants
+
+my $strCurrentAuditLog;		# pg_audit.log setting was Postgres was started with
+my $strTemporaryAuditLog;	# pg_audit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXPLAIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+	               " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		PgExecuteOnly($strSql);
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = $oData;
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = ${$oTableHash}{&NAME};
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   # " -c log_destination='stderr,csvlog'" .
+				   # " -c logging_collector=on" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pg_audit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pg_audit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pg_audit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "grant " . lc(${strPrivilege}) .
+								(defined($strColumn) ? " (${strColumn})" : '') .
+								" on ${strObject} to ${strRole}");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "revoke " . lc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " on ${strObject} from ${strRole}");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable; # Store table info for select, insert, update, delete
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+PgLogExecute(COMMAND_CREATE_TABLE, 'create table test (id int)', 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test', 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+             'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+            &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Now create some email friendly tests.  These first tests are session logging
+# only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+
+# !!! Trying to build test to exclude function calls (did not work)
+# PgLogExecute(COMMAND_CREATE_ROLE, 'create role func_owner');
+# PgAuditLogSet(CONTEXT_ROLE, 'func_owner');
+# PgLogExecute(COMMAND_SET, 'set role func_owner');
+# PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION func_test(a int)' .
+# 									  ' returns int as $$ begin return a + 1;' .
+# 									  ' end $$language plpgsql security definer');
+# PgLogExecute(COMMAND_GRANT, 'grant execute on function func_test(int) to user1');
+
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account (id int, name text, password text,' .
+			 ' description text)', 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Test function without auditing (did not work)
+# PgLogExecute(COMMAND_SELECT, 'select func_test(1)');
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pg_audit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+                'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pg_audit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+PgLogExecute(COMMAND_CREATE_SCHEMA, "create schema test");
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+PgLogExecute(COMMAND_CREATE_TABLE_AS,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 'test.pg_class', true, false);
+PgLogExecute(COMMAND_INSERT,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 undef, false, true);
+PgLogExecute(COMMAND_INSERT,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, true, false);
+PgLogExecute(COMMAND_COPY_FROM,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+		     'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test.test_insert (id int)', 'test.test_insert');
+PgLogExecute(COMMAND_PREPARE_WRITE,
+			 'PREPARE pgclassstmt (oid) as insert' .
+			 ' into test.test_insert (id) values ($1)');
+PgLogExecute(COMMAND_INSERT,
+			 'EXECUTE pgclassstmt (1)', undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_WRITE,
+			 'EXECUTE pgclassstmt (1)', undef, false, true);
+
+# Create a table with a primary key
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test', true, false);
+PgLogExecute(COMMAND_CREATE_INDEX,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+PgLogExecute(COMMAND_GRANT, 'grant select on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+PgLogExecute(COMMAND_GRANT, 'grant select (name) on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Try a select that does not reference any tables
+PgLogExecute(COMMAND_SELECT, 'select 1, current_timestamp');
+
+# Try explain
+PgLogExecute(COMMAND_EXPLAIN, 'explain select 1');
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test drop description', 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test rename to test2', 'public.test');
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test2 set schema test', 'public.test2', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE_INDEX, 'alter table test2 set schema test',
+										'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 add description text',
+								  'test.test2');
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 drop description',
+								  'test.test2');
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, 'drop table test.test2',
+									   'test.test_pkey', false, false);
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test.test2',
+								 'test.test2', true, true);
+
+PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION int_add(a int, b int)' .
+									  ' returns int as $$ begin return a + b;' .
+									  ' end $$language plpgsql');
+PgLogExecute(COMMAND_SELECT, "select int_add(1, 1)",
+							 undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add', false, true);
+
+PgLogExecute(COMMAND_CREATE_AGGREGATE, "CREATE AGGREGATE sum_test (int)" .
+							" (sfunc = int_add, stype = int, initcond = 0)");
+PgLogExecute(COMMAND_ALTER_AGGREGATE,
+			 "ALTER AGGREGATE sum_test (int) rename to sum_test2");
+
+PgLogExecute(COMMAND_CREATE_COLLATION,
+			 "CREATE COLLATION collation_test FROM \"de_DE\"");
+PgLogExecute(COMMAND_ALTER_COLLATION,
+			 "ALTER COLLATION collation_test rename to collation_test2");
+
+PgLogExecute(COMMAND_CREATE_CONVERSION,
+			 "CREATE CONVERSION conversion_test FOR 'SQL_ASCII' TO".
+			 " 'MULE_INTERNAL' FROM ascii_to_mic");
+PgLogExecute(COMMAND_ALTER_CONVERSION,
+			 "ALTER CONVERSION conversion_test rename to conversion_test2");
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..e4f0bdc 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -124,6 +124,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..78565de
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,316 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pg_audit" xreflabel="pg_audit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pg_audit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pg_audit.log</literal>
+        GUC. There are five classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pg_audit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pg_audit.log = 'all, -misc'
+          </programlisting>
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pg_audit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pg_audit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Author</title>
+
+    <para>
+      David Steele <email>[email protected]</email>
+    </para>
+  </sect2>
+</sect1>


  [application/pgp-signature] signature.asc (819B, ../../[email protected]/3-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-02-24 16:22  David Steele <[email protected]>
  parent: David Steele <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: David Steele @ 2015-02-24 16:22 UTC (permalink / raw)
  To: Stephen Frost <[email protected]>; +Cc: pgsql-hackers; Abhijit Menon-Sen <[email protected]>

On 2/23/15 10:59 AM, David Steele wrote:
> On 2/17/15 10:34 AM, Stephen Frost wrote:
>> There seems to be a number of places which are 'pgaudit' and a bunch
>> that are 'pg_audit'.  I'm guessing you were thinking 'pg_audit', but
>> it'd be good to clean up and make them all consistent.
> 
> Fixed, though I still left the file name as pgaudit.sgml since all but
> one module follow that convention.

It turns out there are a few places where _ is not allowed.  I've
reverted a few places to fix the doc build while maintaining the name as
pg_audit in the visible docs.

>> Perhaps I missed it, but it'd be good to point out that GUCs can be set
>> at various levels.  I know we probably say that somewhere else, but it's
>> particularly relevant for this.

I added notes as suggested.

Patch v3 is attached.

-- 
- David Steele
[email protected]

diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..2eee3b9
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,4 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..ead65a8
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1102 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements (See
+ * pg_audit.sgml for details).
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_audit/pg_audit.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String constants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC		    "MISC"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* SELECT */
+	LOG_READ = (1 << 0),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 1),
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 2),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 4),
+
+	/* Function execution */
+	LOG_MISC = (1 << 5),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String constants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If an underlying command affects multiple objects multiple
+ * AuditEvents must be created to represent it.
+ */
+typedef struct
+{
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	bool granted;
+} AuditEvent;
+
+/*
+ * Set if a function below log_utility_command() has logged the event - prevents
+ * more than one function from logging when the event could be logged in
+ * multiple places.
+ */
+bool utilityCommandLogged = false;
+bool utilityCommandInProgress = false;
+AuditEvent utilityAuditEvent;
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEvent *e)
+{
+	const char *classname;
+
+	/* Check that this event should be logged. */
+	if (!log_check(e, &classname))
+		return;
+
+	/* Log via ereport(). */
+	ereport(LOG,
+			(errmsg("AUDIT: %s,%s,%s,%s,%s,%s",
+					e->granted ? AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+					classname, e->command, e->objectType, e->objectName,
+					e->commandText),
+			 errhidestmt(true)));
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem    *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* bms_first_member is destructive, so make a copy before using it. */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for DML operations via executor permissions checks.
+ */
+static void
+log_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+	AuditEvent auditEvent;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		auditEvent.logStmtLevel = LOGSTMT_MOD;
+
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEvent.commandTag = T_InsertStmt;
+			auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEvent.commandTag = T_UpdateStmt;
+			auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEvent.commandTag = T_DeleteStmt;
+			auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEvent.commandTag = T_SelectStmt;
+			auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEvent.commandTag = T_Invalid;
+			auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEvent.objectName = "";
+			auditEvent.objectType = "";
+
+			log_audit_event(&auditEvent);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEvent.objectType = OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEvent.objectType = OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEvent.granted)
+		{
+			log_audit_event(&auditEvent);
+		}
+
+		pfree(auditEvent.objectName);
+	}
+
+	/*
+	 * If the first flag was never set to false, then rangeTabls was empty. In
+	 * this case log a session select statement.
+	 */
+	if (first && !utilityCommandInProgress)
+	{
+		auditEvent.logStmtLevel = LOGSTMT_ALL;
+		auditEvent.commandTag = T_SelectStmt;
+		auditEvent.command = COMMAND_SELECT;
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+		auditEvent.objectName = "";
+		auditEvent.objectType = "";
+
+		log_audit_event(&auditEvent);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		utilityAuditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				utilityAuditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+
+		/* Log the event */
+		log_audit_event(&utilityAuditEvent);
+		utilityCommandLogged = true;
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Generate the fully-qualified function name. */
+	utilityAuditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the event */
+	utilityAuditEvent.logStmtLevel = LOGSTMT_ALL;
+	utilityAuditEvent.commandTag = T_DoStmt;
+	utilityAuditEvent.command = COMMAND_EXECUTE;
+	utilityAuditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	utilityAuditEvent.commandText = debug_query_string;
+
+	log_audit_event(&utilityAuditEvent);
+	utilityCommandLogged = true;
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute. */
+		case OAT_FUNCTION_EXECUTE:
+			log_function_execute(objectId);
+			break;
+
+		/* Log create. */
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter. */
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop. */
+		case OAT_DROP:
+		{
+			ObjectAccessDrop *drop = arg;
+
+			if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+				return;
+
+			log_create_alter_drop(classId, objectId);
+		}
+		break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid;
+
+	/* Get the audit oid if the role exists. */
+	auditOid = get_role_oid(auditRole, true);
+
+	/* Log DML if the audit role is valid or session logging is enabled. */
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_dml(auditOid, rangeTabls);
+
+	/* Call the next hook function. */
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	/* Create the utility audit event. */
+	utilityCommandLogged = false;
+	utilityCommandInProgress = true;
+
+	utilityAuditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+	utilityAuditEvent.commandTag = nodeTag(parsetree);
+	utilityAuditEvent.command = CreateCommandTag(parsetree);
+	utilityAuditEvent.objectName = "";
+	utilityAuditEvent.objectType = "";
+	utilityAuditEvent.commandText = debug_query_string;
+	utilityAuditEvent.granted = false;
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Log the utility command if logging is on, the command has not already
+	 * been logged by another hook, and the transaction is not aborted */
+	if (auditLogBitmap != 0 && !utilityCommandLogged &&
+		!IsAbortedTransactionBlockState())
+	{
+		log_audit_event(&utilityAuditEvent);
+	}
+
+	utilityCommandInProgress = false;
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState())
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pg_audit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("pg_audit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pg_audit.role = "audit"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pg_audit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pg_audit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pg_audit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..0b39082
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pgaudit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..dcb9fda
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1243 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pg_audit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE				=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE		=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION		=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION	=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE		=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE			=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET		=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE			=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_INDEX	=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN				=> 'BEGIN',
+	COMMAND_CLOSE				=> 'CLOSE CURSOR',
+	COMMAND_COMMIT				=> 'COMMIT',
+	COMMAND_COPY				=> 'COPY',
+	COMMAND_COPY_TO				=> 'COPY TO',
+	COMMAND_COPY_FROM			=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE	=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION	=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION	=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE		=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX		=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE			=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR		=> 'DECLARE CURSOR',
+	COMMAND_DO					=> 'DO',
+	COMMAND_DISCARD_ALL			=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION		=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE			=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA		=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE		=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS		=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE		=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA			=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE			=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_INDEX	=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TYPE		=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE				=> 'EXECUTE',
+	COMMAND_EXECUTE_READ		=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE		=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION	=> 'EXECUTE FUNCTION',
+	COMMAND_EXPLAIN				=> 'EXPLAIN',
+	COMMAND_FETCH				=> 'FETCH',
+	COMMAND_GRANT				=> 'GRANT',
+	COMMAND_INSERT				=> 'INSERT',
+	COMMAND_PREPARE				=> 'PREPARE',
+	COMMAND_PREPARE_READ		=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE		=> 'PREPARE WRITE',
+	COMMAND_REVOKE				=> 'REVOKE',
+	COMMAND_SELECT				=> 'SELECT',
+	COMMAND_SET					=> 'SET',
+	COMMAND_UPDATE				=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE			=> 'TYPE',
+	TYPE_NONE		=> '',
+
+	TYPE_FUNCTION	=> 'FUNCTION',
+	TYPE_INDEX		=> 'INDEX',
+	TYPE_TABLE		=> 'TABLE',
+	TYPE_TYPE		=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pg_audit.log GUCS
+my %oAuditGrantHash;			# Hash to store pg_audit grants
+
+my $strCurrentAuditLog;		# pg_audit.log setting was Postgres was started with
+my $strTemporaryAuditLog;	# pg_audit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXPLAIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+	               " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		PgExecuteOnly($strSql);
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = $oData;
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = ${$oTableHash}{&NAME};
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   # " -c log_destination='stderr,csvlog'" .
+				   # " -c logging_collector=on" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pg_audit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pg_audit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pg_audit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "grant " . lc(${strPrivilege}) .
+								(defined($strColumn) ? " (${strColumn})" : '') .
+								" on ${strObject} to ${strRole}");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "revoke " . lc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " on ${strObject} from ${strRole}");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable; # Store table info for select, insert, update, delete
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+PgLogExecute(COMMAND_CREATE_TABLE, 'create table test (id int)', 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test', 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+             'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+            &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Now create some email friendly tests.  These first tests are session logging
+# only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+
+# !!! Trying to build test to exclude function calls (did not work)
+# PgLogExecute(COMMAND_CREATE_ROLE, 'create role func_owner');
+# PgAuditLogSet(CONTEXT_ROLE, 'func_owner');
+# PgLogExecute(COMMAND_SET, 'set role func_owner');
+# PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION func_test(a int)' .
+# 									  ' returns int as $$ begin return a + 1;' .
+# 									  ' end $$language plpgsql security definer');
+# PgLogExecute(COMMAND_GRANT, 'grant execute on function func_test(int) to user1');
+
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account (id int, name text, password text,' .
+			 ' description text)', 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Test function without auditing (did not work)
+# PgLogExecute(COMMAND_SELECT, 'select func_test(1)');
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pg_audit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+                'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pg_audit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+PgLogExecute(COMMAND_CREATE_SCHEMA, "create schema test");
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+PgLogExecute(COMMAND_CREATE_TABLE_AS,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 'test.pg_class', true, false);
+PgLogExecute(COMMAND_INSERT,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 undef, false, true);
+PgLogExecute(COMMAND_INSERT,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, true, false);
+PgLogExecute(COMMAND_COPY_FROM,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+		     'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test.test_insert (id int)', 'test.test_insert');
+PgLogExecute(COMMAND_PREPARE_WRITE,
+			 'PREPARE pgclassstmt (oid) as insert' .
+			 ' into test.test_insert (id) values ($1)');
+PgLogExecute(COMMAND_INSERT,
+			 'EXECUTE pgclassstmt (1)', undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_WRITE,
+			 'EXECUTE pgclassstmt (1)', undef, false, true);
+
+# Create a table with a primary key
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test', true, false);
+PgLogExecute(COMMAND_CREATE_INDEX,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+PgLogExecute(COMMAND_GRANT, 'grant select on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+PgLogExecute(COMMAND_GRANT, 'grant select (name) on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Try a select that does not reference any tables
+PgLogExecute(COMMAND_SELECT, 'select 1, current_timestamp');
+
+# Try explain
+PgLogExecute(COMMAND_EXPLAIN, 'explain select 1');
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test drop description', 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test rename to test2', 'public.test');
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test2 set schema test', 'public.test2', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE_INDEX, 'alter table test2 set schema test',
+										'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 add description text',
+								  'test.test2');
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 drop description',
+								  'test.test2');
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, 'drop table test.test2',
+									   'test.test_pkey', false, false);
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test.test2',
+								 'test.test2', true, true);
+
+PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION int_add(a int, b int)' .
+									  ' returns int as $$ begin return a + b;' .
+									  ' end $$language plpgsql');
+PgLogExecute(COMMAND_SELECT, "select int_add(1, 1)",
+							 undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add', false, true);
+
+PgLogExecute(COMMAND_CREATE_AGGREGATE, "CREATE AGGREGATE sum_test (int)" .
+							" (sfunc = int_add, stype = int, initcond = 0)");
+PgLogExecute(COMMAND_ALTER_AGGREGATE,
+			 "ALTER AGGREGATE sum_test (int) rename to sum_test2");
+
+PgLogExecute(COMMAND_CREATE_COLLATION,
+			 "CREATE COLLATION collation_test FROM \"de_DE\"");
+PgLogExecute(COMMAND_ALTER_COLLATION,
+			 "ALTER COLLATION collation_test rename to collation_test2");
+
+PgLogExecute(COMMAND_CREATE_CONVERSION,
+			 "CREATE CONVERSION conversion_test FOR 'SQL_ASCII' TO".
+			 " 'MULE_INTERNAL' FROM ascii_to_mic");
+PgLogExecute(COMMAND_ALTER_CONVERSION,
+			 "ALTER CONVERSION conversion_test rename to conversion_test2");
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..e4f0bdc 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -124,6 +124,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..d62d45f
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,330 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pgaudit" xreflabel="pgaudit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pgaudit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pg_audit.log</literal>
+        GUC. There are five classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pg_audit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pg_audit.log = 'all, -misc'
+          </programlisting>
+      </para>
+      
+      <para>
+      Note that <literal>pg_audit.log</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pg_audit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pg_audit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+
+      <para>
+      Note that <literal>pg_audit.role</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Authors</title>
+
+    <para>
+      Abhijit Menon-Sen <email>[email protected]</email>, Ian Barwick <email>[email protected]</email>, and David Steele <email>[email protected]</email>.
+    </para>
+  </sect2>
+</sect1>


Attachments:

  [text/plain] pg_audit-v3.patch (78.2K, ../../[email protected]/2-pg_audit-v3.patch)
  download | inline diff:
diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..2eee3b9
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,4 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..ead65a8
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1102 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements (See
+ * pg_audit.sgml for details).
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_audit/pg_audit.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String constants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC		    "MISC"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* SELECT */
+	LOG_READ = (1 << 0),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 1),
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 2),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 4),
+
+	/* Function execution */
+	LOG_MISC = (1 << 5),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String constants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If an underlying command affects multiple objects multiple
+ * AuditEvents must be created to represent it.
+ */
+typedef struct
+{
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	bool granted;
+} AuditEvent;
+
+/*
+ * Set if a function below log_utility_command() has logged the event - prevents
+ * more than one function from logging when the event could be logged in
+ * multiple places.
+ */
+bool utilityCommandLogged = false;
+bool utilityCommandInProgress = false;
+AuditEvent utilityAuditEvent;
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEvent *e)
+{
+	const char *classname;
+
+	/* Check that this event should be logged. */
+	if (!log_check(e, &classname))
+		return;
+
+	/* Log via ereport(). */
+	ereport(LOG,
+			(errmsg("AUDIT: %s,%s,%s,%s,%s,%s",
+					e->granted ? AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+					classname, e->command, e->objectType, e->objectName,
+					e->commandText),
+			 errhidestmt(true)));
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem    *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* bms_first_member is destructive, so make a copy before using it. */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for DML operations via executor permissions checks.
+ */
+static void
+log_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+	AuditEvent auditEvent;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		auditEvent.logStmtLevel = LOGSTMT_MOD;
+
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEvent.commandTag = T_InsertStmt;
+			auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEvent.commandTag = T_UpdateStmt;
+			auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEvent.commandTag = T_DeleteStmt;
+			auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEvent.commandTag = T_SelectStmt;
+			auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEvent.commandTag = T_Invalid;
+			auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEvent.objectName = "";
+			auditEvent.objectType = "";
+
+			log_audit_event(&auditEvent);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEvent.objectType = OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEvent.objectType = OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEvent.granted)
+		{
+			log_audit_event(&auditEvent);
+		}
+
+		pfree(auditEvent.objectName);
+	}
+
+	/*
+	 * If the first flag was never set to false, then rangeTabls was empty. In
+	 * this case log a session select statement.
+	 */
+	if (first && !utilityCommandInProgress)
+	{
+		auditEvent.logStmtLevel = LOGSTMT_ALL;
+		auditEvent.commandTag = T_SelectStmt;
+		auditEvent.command = COMMAND_SELECT;
+		auditEvent.granted = false;
+		auditEvent.commandText = debug_query_string;
+		auditEvent.objectName = "";
+		auditEvent.objectType = "";
+
+		log_audit_event(&auditEvent);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		utilityAuditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				utilityAuditEvent.objectType = OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				utilityAuditEvent.objectType = OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				utilityAuditEvent.objectType = OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+
+		/* Log the event */
+		log_audit_event(&utilityAuditEvent);
+		utilityCommandLogged = true;
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Generate the fully-qualified function name. */
+	utilityAuditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the event */
+	utilityAuditEvent.logStmtLevel = LOGSTMT_ALL;
+	utilityAuditEvent.commandTag = T_DoStmt;
+	utilityAuditEvent.command = COMMAND_EXECUTE;
+	utilityAuditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	utilityAuditEvent.commandText = debug_query_string;
+
+	log_audit_event(&utilityAuditEvent);
+	utilityCommandLogged = true;
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute. */
+		case OAT_FUNCTION_EXECUTE:
+			log_function_execute(objectId);
+			break;
+
+		/* Log create. */
+		case OAT_POST_CREATE:
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter. */
+		case OAT_POST_ALTER:
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop. */
+		case OAT_DROP:
+		{
+			ObjectAccessDrop *drop = arg;
+
+			if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+				return;
+
+			log_create_alter_drop(classId, objectId);
+		}
+		break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid;
+
+	/* Get the audit oid if the role exists. */
+	auditOid = get_role_oid(auditRole, true);
+
+	/* Log DML if the audit role is valid or session logging is enabled. */
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_dml(auditOid, rangeTabls);
+
+	/* Call the next hook function. */
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	/* Create the utility audit event. */
+	utilityCommandLogged = false;
+	utilityCommandInProgress = true;
+
+	utilityAuditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+	utilityAuditEvent.commandTag = nodeTag(parsetree);
+	utilityAuditEvent.command = CreateCommandTag(parsetree);
+	utilityAuditEvent.objectName = "";
+	utilityAuditEvent.objectType = "";
+	utilityAuditEvent.commandText = debug_query_string;
+	utilityAuditEvent.granted = false;
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Log the utility command if logging is on, the command has not already
+	 * been logged by another hook, and the transaction is not aborted */
+	if (auditLogBitmap != 0 && !utilityCommandLogged &&
+		!IsAbortedTransactionBlockState())
+	{
+		log_audit_event(&utilityAuditEvent);
+	}
+
+	utilityCommandInProgress = false;
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState())
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pg_audit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("pg_audit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pg_audit.role = "audit"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pg_audit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pg_audit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pg_audit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..0b39082
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pgaudit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..dcb9fda
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1243 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pg_audit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE				=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE		=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION		=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION	=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE		=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE			=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET		=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE			=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_INDEX	=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN				=> 'BEGIN',
+	COMMAND_CLOSE				=> 'CLOSE CURSOR',
+	COMMAND_COMMIT				=> 'COMMIT',
+	COMMAND_COPY				=> 'COPY',
+	COMMAND_COPY_TO				=> 'COPY TO',
+	COMMAND_COPY_FROM			=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE	=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION	=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION	=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE		=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX		=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE			=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR		=> 'DECLARE CURSOR',
+	COMMAND_DO					=> 'DO',
+	COMMAND_DISCARD_ALL			=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION		=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE			=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA		=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE		=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS		=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE		=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA			=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE			=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_INDEX	=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TYPE		=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE				=> 'EXECUTE',
+	COMMAND_EXECUTE_READ		=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE		=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION	=> 'EXECUTE FUNCTION',
+	COMMAND_EXPLAIN				=> 'EXPLAIN',
+	COMMAND_FETCH				=> 'FETCH',
+	COMMAND_GRANT				=> 'GRANT',
+	COMMAND_INSERT				=> 'INSERT',
+	COMMAND_PREPARE				=> 'PREPARE',
+	COMMAND_PREPARE_READ		=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE		=> 'PREPARE WRITE',
+	COMMAND_REVOKE				=> 'REVOKE',
+	COMMAND_SELECT				=> 'SELECT',
+	COMMAND_SET					=> 'SET',
+	COMMAND_UPDATE				=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE			=> 'TYPE',
+	TYPE_NONE		=> '',
+
+	TYPE_FUNCTION	=> 'FUNCTION',
+	TYPE_INDEX		=> 'INDEX',
+	TYPE_TABLE		=> 'TABLE',
+	TYPE_TYPE		=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pg_audit.log GUCS
+my %oAuditGrantHash;			# Hash to store pg_audit grants
+
+my $strCurrentAuditLog;		# pg_audit.log setting was Postgres was started with
+my $strTemporaryAuditLog;	# pg_audit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXPLAIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+	               " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+	               " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		PgExecuteOnly($strSql);
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = $oData;
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = ${$oTableHash}{&NAME};
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   # " -c log_destination='stderr,csvlog'" .
+				   # " -c logging_collector=on" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pg_audit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pg_audit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pg_audit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "grant " . lc(${strPrivilege}) .
+								(defined($strColumn) ? " (${strColumn})" : '') .
+								" on ${strObject} to ${strRole}");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "revoke " . lc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " on ${strObject} from ${strRole}");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable; # Store table info for select, insert, update, delete
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+PgLogExecute(COMMAND_CREATE_TABLE, 'create table test (id int)', 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test', 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+             'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+             'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+            &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Now create some email friendly tests.  These first tests are session logging
+# only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+
+# !!! Trying to build test to exclude function calls (did not work)
+# PgLogExecute(COMMAND_CREATE_ROLE, 'create role func_owner');
+# PgAuditLogSet(CONTEXT_ROLE, 'func_owner');
+# PgLogExecute(COMMAND_SET, 'set role func_owner');
+# PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION func_test(a int)' .
+# 									  ' returns int as $$ begin return a + 1;' .
+# 									  ' end $$language plpgsql security definer');
+# PgLogExecute(COMMAND_GRANT, 'grant execute on function func_test(int) to user1');
+
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account (id int, name text, password text,' .
+			 ' description text)', 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Test function without auditing (did not work)
+# PgLogExecute(COMMAND_SELECT, 'select func_test(1)');
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pg_audit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+                'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+             &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pg_audit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+PgLogExecute(COMMAND_CREATE_SCHEMA, "create schema test");
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+PgLogExecute(COMMAND_CREATE_TABLE_AS,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 'test.pg_class', true, false);
+PgLogExecute(COMMAND_INSERT,
+			 "CREATE TABLE test.pg_class as select * from pg_class",
+			 undef, false, true);
+PgLogExecute(COMMAND_INSERT,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, true, false);
+PgLogExecute(COMMAND_COPY_FROM,
+			 "COPY test.pg_class from '" . abs_path($strTestPath) .
+			 "/class.out'", undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+		     'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test.test_insert (id int)', 'test.test_insert');
+PgLogExecute(COMMAND_PREPARE_WRITE,
+			 'PREPARE pgclassstmt (oid) as insert' .
+			 ' into test.test_insert (id) values ($1)');
+PgLogExecute(COMMAND_INSERT,
+			 'EXECUTE pgclassstmt (1)', undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_WRITE,
+			 'EXECUTE pgclassstmt (1)', undef, false, true);
+
+# Create a table with a primary key
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test', true, false);
+PgLogExecute(COMMAND_CREATE_INDEX,
+			 'create table test (id int primary key, name text,' .
+			 'description text)',
+			 'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+PgLogExecute(COMMAND_GRANT, 'grant select on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+PgLogExecute(COMMAND_GRANT, 'grant select (name) on public.test to public');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Try a select that does not reference any tables
+PgLogExecute(COMMAND_SELECT, 'select 1, current_timestamp');
+
+# Try explain
+PgLogExecute(COMMAND_EXPLAIN, 'explain select 1');
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test drop description', 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test rename to test2', 'public.test');
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 'alter table test2 set schema test', 'public.test2', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE_INDEX, 'alter table test2 set schema test',
+										'public.test_pkey', false, true);
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 add description text',
+								  'test.test2');
+PgLogExecute(COMMAND_ALTER_TABLE, 'alter table test.test2 drop description',
+								  'test.test2');
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, 'drop table test.test2',
+									   'test.test_pkey', false, false);
+PgLogExecute(COMMAND_DROP_TABLE, 'drop table test.test2',
+								 'test.test2', true, true);
+
+PgLogExecute(COMMAND_CREATE_FUNCTION, 'CREATE FUNCTION int_add(a int, b int)' .
+									  ' returns int as $$ begin return a + b;' .
+									  ' end $$language plpgsql');
+PgLogExecute(COMMAND_SELECT, "select int_add(1, 1)",
+							 undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add', false, true);
+
+PgLogExecute(COMMAND_CREATE_AGGREGATE, "CREATE AGGREGATE sum_test (int)" .
+							" (sfunc = int_add, stype = int, initcond = 0)");
+PgLogExecute(COMMAND_ALTER_AGGREGATE,
+			 "ALTER AGGREGATE sum_test (int) rename to sum_test2");
+
+PgLogExecute(COMMAND_CREATE_COLLATION,
+			 "CREATE COLLATION collation_test FROM \"de_DE\"");
+PgLogExecute(COMMAND_ALTER_COLLATION,
+			 "ALTER COLLATION collation_test rename to collation_test2");
+
+PgLogExecute(COMMAND_CREATE_CONVERSION,
+			 "CREATE CONVERSION conversion_test FOR 'SQL_ASCII' TO".
+			 " 'MULE_INTERNAL' FROM ascii_to_mic");
+PgLogExecute(COMMAND_ALTER_CONVERSION,
+			 "ALTER CONVERSION conversion_test rename to conversion_test2");
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..e4f0bdc 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -124,6 +124,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..d62d45f
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,330 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pgaudit" xreflabel="pgaudit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pgaudit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pg_audit.log</literal>
+        GUC. There are five classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pg_audit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pg_audit.log = 'all, -misc'
+          </programlisting>
+      </para>
+      
+      <para>
+      Note that <literal>pg_audit.log</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pg_audit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pg_audit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+
+      <para>
+      Note that <literal>pg_audit.role</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Authors</title>
+
+    <para>
+      Abhijit Menon-Sen <email>[email protected]</email>, Ian Barwick <email>[email protected]</email>, and David Steele <email>[email protected]</email>.
+    </para>
+  </sect2>
+</sect1>


  [application/pgp-signature] signature.asc (819B, ../../[email protected]/3-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-23 05:31  Abhijit Menon-Sen <[email protected]>
  parent: David Steele <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: Abhijit Menon-Sen @ 2015-03-23 05:31 UTC (permalink / raw)
  To: David Steele <[email protected]>; +Cc: Stephen Frost <[email protected]>; Álvaro Herrera <[email protected]>; pgsql-hackers

At 2015-02-24 11:22:41 -0500, [email protected] wrote:
>
> Patch v3 is attached.
>
> […]
>
> +/* Log class enum used to represent bits in auditLogBitmap */
> +enum LogClass
> +{
> +	LOG_NONE = 0,
> +
> +	/* SELECT */
> +	LOG_READ = (1 << 0),
> +
> +	/* INSERT, UPDATE, DELETE, TRUNCATE */
> +	LOG_WRITE = (1 << 1),
> +
> +	/* DDL: CREATE/DROP/ALTER */
> +	LOG_DDL = (1 << 2),
> +
> +	/* Function execution */
> +	LOG_FUNCTION = (1 << 4),
> +
> +	/* Function execution */
> +	LOG_MISC = (1 << 5),
> +
> +	/* Absolutely everything */
> +	LOG_ALL = ~(uint64)0
> +};

The comment above LOG_MISC should be changed.

More fundamentally, this classification makes it easy to reuse LOGSTMT_*
(and a nice job you've done of that, with just a few additional special
cases), I don't think this level is quite enough for our needs. I think
it should at least be possible to specifically log commands that affect
privileges and roles.

I'm fond of finer categorisation for DDL as well, but I could live with
all DDL being lumped together.

I'm experimenting with a few approaches to do this without reintroducing
switch statements to test every command. That will require core changes,
but I think we can find an acceptable arrangement. I'll post a proof of
concept in a few days.

> + * Takes an AuditEvent and, if it log_check(), writes it to the audit
> log.

I don't think log_check is the most useful name, because this sentence
doesn't tell me what the function may do. Similarly, I would prefer to
have log_acl_check be renamed acl_grants_audit or similar. (These are
all static functions anyway, I don't think a log_ prefix is needed.)

> +	/* Free the column set */
> +	bms_free(tmpSet);

(An aside, really: there are lots of comments like this, which I don't
think add anything to understanding the code, and should be removed.)

> +		/*
> +		 * We don't have access to the parsetree here, so we have to generate
> +		 * the node type, object type, and command tag by decoding
> +		 * rte->requiredPerms and rte->relkind.
> +		 */
> +		auditEvent.logStmtLevel = LOGSTMT_MOD;

(I am also trying to find a way to avoid having to do this.)

> +		/* Set object type based on relkind */
> +		switch (class->relkind)
> +		{
> +			case RELKIND_RELATION:
> +				utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
> +				break;

This occurs elsewhere too. But I suppose new relkinds are added less
frequently than new commands.

Again on a larger level, I'm not sure how I feel about _avoiding_ the
use of event triggers for audit logging. Regardless of whether we use
the deparse code (which I personally think is a good idea; Álvaro has
been working on it, and it looks very nice) to log extra information,
using the object access hook inevitably means we have to reimplement
the identification/classification code here.

In "old" pgaudit, I think that extra effort is justified by the need to
be backwards compatible with pre-event trigger releases. In a 9.5-only
version, I am not at all convinced that this makes sense.

Thoughts?

-- Abhijit


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-23 16:40  David Steele <[email protected]>
  parent: Abhijit Menon-Sen <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: David Steele @ 2015-03-23 16:40 UTC (permalink / raw)
  To: Abhijit Menon-Sen <[email protected]>; +Cc: Stephen Frost <[email protected]>; Álvaro Herrera <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Thanks for the review, Abhijit.

On 3/23/15 1:31 AM, Abhijit Menon-Sen wrote:
> At 2015-02-24 11:22:41 -0500, [email protected] wrote:
>>
>> Patch v3 is attached.
>> +
>> +	/* Function execution */
>> +	LOG_MISC = (1 << 5),
> 
> The comment above LOG_MISC should be changed.

Fixed.

> More fundamentally, this classification makes it easy to reuse LOGSTMT_*
> (and a nice job you've done of that, with just a few additional special
> cases), I don't think this level is quite enough for our needs. I think
> it should at least be possible to specifically log commands that affect
> privileges and roles.

I agree, but this turns out to be easier said than done.  In the prior
code for instance, CREATE ROLE was classified as USER, while ALTER ROLE
.. RENAME was classified as DDL.  This is because any rename gets the
command tag T_RenameStmt.  CreateCommandTag does return "ALTER ROLE",
but now we're in the realm of string-matching again which is not my
favorite thing.  Let me see if there is a clean way to get this
accomplished.  I've also felt this is the one thing I'd like to see
broken out.

> I'm fond of finer categorisation for DDL as well, but I could live with
> all DDL being lumped together.
> 
> I'm experimenting with a few approaches to do this without reintroducing
> switch statements to test every command. That will require core changes,
> but I think we can find an acceptable arrangement. I'll post a proof of
> concept in a few days.

I also think finer-grained categorization would be best accomplished
with some core changes.  It seemed too late to get those in for 9.5 so I
decided to proceed with what I knew could be done reliably with the idea
to improve it with core changes going forward.

I look forward to your proof-of-concept.

>> + * Takes an AuditEvent and, if it log_check(), writes it to the audit
>> log.
> 
> I don't think log_check is the most useful name, because this sentence
> doesn't tell me what the function may do. Similarly, I would prefer to
> have log_acl_check be renamed acl_grants_audit or similar. (These are
> all static functions anyway, I don't think a log_ prefix is needed.)

log_check() has become somewhat vestigial at this point since it is only
called from one place - I've been considering removing it and merging
into log_audit_event().  For the moment I've improved the comments.

I like acl_grants_audit() and agree that it's a clearer name.  I'll
incorporate that into the next version and apply the same scheme to the
other ACL functionsas well as do a general review of naming.

>> +	/* Free the column set */
>> +	bms_free(tmpSet);
> 
> (An aside, really: there are lots of comments like this, which I don't
> think add anything to understanding the code, and should be removed.)

I generally feel like you can't have too many comments.  I think even
the less interesting/helpful comments help break the code into
functional sections for readability.

>> +		/*
>> +		 * We don't have access to the parsetree here, so we have to generate
>> +		 * the node type, object type, and command tag by decoding
>> +		 * rte->requiredPerms and rte->relkind.
>> +		 */
>> +		auditEvent.logStmtLevel = LOGSTMT_MOD;
> 
> (I am also trying to find a way to avoid having to do this.)

That would be excellent.

>> +		/* Set object type based on relkind */
>> +		switch (class->relkind)
>> +		{
>> +			case RELKIND_RELATION:
>> +				utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
>> +				break;
> 
> This occurs elsewhere too. But I suppose new relkinds are added less
> frequently than new commands.

Well, that's the hope at least.  I should mention that ALL statements
will be logged no matter what additional classification happens.  The
amount of information returned may not be ideal, but nothing is ever
excluded from logging (depending on the classes selected, of course).

> Again on a larger level, I'm not sure how I feel about _avoiding_ the
> use of event triggers for audit logging. Regardless of whether we use
> the deparse code (which I personally think is a good idea; Álvaro has
> been working on it, and it looks very nice) to log extra information,
> using the object access hook inevitably means we have to reimplement
> the identification/classification code here.
> 
> In "old" pgaudit, I think that extra effort is justified by the need to
> be backwards compatible with pre-event trigger releases. In a 9.5-only
> version, I am not at all convinced that this makes sense.
> 
> Thoughts?

I was nervous about basing pg_audit on code that I wasn't sure would be
committed (at the time).  Since pg_event_trigger_get_creation_commands()
is tied up with deparse, I honestly didn't feel like the triggers were
bringing much to the table.

That being said, I agree that the deparse code is very useful and now
looks certain to be committed for 9.5.

I have prepared a patch that brings event triggers and deparse back to
pg_audit based on the Alvaro's dev/deparse branch at
git://git.postgresql.org/git/2ndquadrant_bdr.git (commit 0447fc5).  I've
updated the unit tests accordingly.

I left in the OAT code for now.  It does add detail to one event that
the event triggers do not handle (creating PK indexes) and I feel that
it lends an element of safety in case something happens to the event
triggers.  OAT is also required for function calls so the code path
cannot be eliminated entirely.  I'm not committed to keeping the
redundant OAT code, but I'd rather not remove it until deparse is
committed to master.

I've been hesitant to post this patch as it will not work in master
(though it will compile), but I don't want to hold on to it any longer
since the end of the CF is nominally just weeks away.  If you want to
run the patch in master, you'll need to disable the
pg_audit_ddl_command_end trigger.

I've also addressed Fujii's concerns about logging parameters - this is
now an option.  The event stack has been formalized and
MemoryContextRegisterResetCallback() is used to cleanup the stack on errors.

Let me know what you think.

-- 
- David Steele
[email protected]

diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..9d9ee83
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,22 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
+
+CREATE FUNCTION pg_audit_ddl_command_end()
+	RETURNS event_trigger
+	LANGUAGE C
+	AS 'MODULE_PATHNAME', 'pg_audit_ddl_command_end';
+
+CREATE EVENT TRIGGER pg_audit_ddl_command_end
+	ON ddl_command_end
+	EXECUTE PROCEDURE pg_audit_ddl_command_end();
+
+CREATE FUNCTION pg_audit_sql_drop()
+	RETURNS event_trigger
+	LANGUAGE C
+	AS 'MODULE_PATHNAME', 'pg_audit_sql_drop';
+
+CREATE EVENT TRIGGER pg_audit_sql_drop
+	ON sql_drop
+	EXECUTE PROCEDURE pg_audit_sql_drop();
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..6c24650
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1716 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements (See
+ * pg_audit.sgml for details).
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_audit/pg_audit.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * Event trigger prototypes
+ */
+Datum pg_audit_ddl_command_end(PG_FUNCTION_ARGS);
+Datum pg_audit_sql_drop(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(pg_audit_ddl_command_end);
+PG_FUNCTION_INFO_V1(pg_audit_sql_drop);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String constants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC			"MISC"
+#define CLASS_PARAMETER		"PARAMETER"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 1),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 2),
+
+	/* Statements not covered by another class */
+	LOG_MISC = (1 << 3),
+
+	/* Function execution */
+	LOG_PARAMETER = (1 << 4),
+
+	/* SELECT */
+	LOG_READ = (1 << 5),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 6),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String constants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If a statement affects multiple objects multiple AuditEvents must be
+ * created to represent it.
+ */
+typedef struct
+{
+	int64 statementId;
+	int64 substatementId;
+
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	ParamListInfo paramList;
+
+	bool granted;
+	bool logged;
+} AuditEvent;
+
+/*
+ * A simple FIFO queue to keep track of the current stack of audit events.
+ */
+typedef struct AuditEventStackItem
+{
+	struct AuditEventStackItem *next;
+
+	AuditEvent auditEvent;
+
+	MemoryContext contextAudit;
+	MemoryContextCallback contextCallback;
+} AuditEventStackItem;
+
+AuditEventStackItem *auditEventStack = NULL;
+
+/*
+ * Track when an internal statement is running so it is not logged
+ */
+static bool internalStatement = false;
+
+/*
+ * Track running total for statements and substatements and whether or not
+ * anything has been logged since this statement began.
+ */
+static uint64 statementTotal = 0;
+static uint64 substatementTotal = 0;
+
+static bool statementLogged = false;
+
+/*
+ * Stack functions
+ *
+ * Audit events can go down to multiple levels so a stack is maintained to keep
+ * track of them.
+ */
+
+/*
+ * Respond to callbacks registered with MemoryContextRegisterResetCallback().
+ * Removes the event(s) off the stack that have become obsolete once the
+ * MemoryContext has been freed.  The callback should always be freeing the top
+ * of the stack, but the code is tolerant of out-of-order callbacks.
+ */
+static void
+stack_free(void *stackFree)
+{
+	AuditEventStackItem *nextItem = auditEventStack;
+
+	/* Only process if the stack contains items */
+	while (nextItem != NULL)
+	{
+		/* Check if this item matches the item to be freed */
+		if (nextItem == (AuditEventStackItem *)stackFree)
+		{
+			/* Move top of stack the the item after the freed item */
+			auditEventStack = nextItem->next;
+
+			/* If the stack is not empty */
+			if (auditEventStack == NULL)
+			{
+				/* Reset internal statement in case of error */
+				internalStatement = false;
+
+				/* Reset sub statement total */
+				substatementTotal = 0;
+
+				/* Reset statement logged flag total */
+				statementLogged = false;
+			}
+
+			return;
+		}
+
+		/* Still looking, test the next item */
+		nextItem = nextItem->next;
+	}
+}
+
+/*
+ * Push a new audit event onto the stack and create a new memory context to
+ * store it.
+ */
+static AuditEventStackItem *
+stack_push()
+{
+	MemoryContext contextAudit;
+	MemoryContext contextOld;
+	AuditEventStackItem *stackItem;
+
+	/* Create a new memory context */
+	contextAudit = AllocSetContextCreate(CurrentMemoryContext,
+										 "pg_audit stack context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+	contextOld = MemoryContextSwitchTo(contextAudit);
+
+	/* Allocate the stack item */
+	stackItem = palloc0(sizeof(AuditEventStackItem));
+
+	/* Store memory contexts */
+	stackItem->contextAudit = contextAudit;
+
+	/* If item already on stack then push it down */
+	if (auditEventStack != NULL)
+		stackItem->next = auditEventStack;
+	else
+		stackItem->next = NULL;
+
+	/*
+	 * Setup a callback in case an error happens.  stack_free() will truncate
+	 * the stack at this item.
+	 */
+	stackItem->contextCallback.func = stack_free;
+	stackItem->contextCallback.arg = (void *)stackItem;
+	MemoryContextRegisterResetCallback(contextAudit,
+									   &stackItem->contextCallback);
+
+	/* Push item on the stack */
+	auditEventStack = stackItem;
+
+	/* Return to the old memory context */
+	MemoryContextSwitchTo(contextOld);
+
+	/* Return the stack item */
+	return stackItem;
+}
+
+/*
+ * Pop an audit event from the stack by deleting the memory context that
+ * contains it.  The callback to stack_free() does the actual pop.
+ */
+static void
+stack_pop()
+{
+	/* Error if the stack is already empty */
+	if (auditEventStack == NULL)
+		elog(ERROR, "pg_audit stack is already empty");
+
+	/* Switch the old memory context and delete the audit context */
+	MemoryContextDelete(auditEventStack->contextAudit);
+}
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Appends a properly quoted CSV field to StringInfo.
+ */
+static void
+append_valid_csv(StringInfoData *buffer, const char *appendStr)
+{
+	const char *pChar;
+
+	/*
+	 * If the append string is null then return.  NULL fields are not quoted
+	 * in CSV
+	 */
+	if (appendStr == NULL)
+		return;
+
+	/* Only format for CSV if appendStr contains: ", comma, \n, \r */
+	if (strstr(appendStr, ",") || strstr(appendStr, "\"") ||
+		strstr(appendStr, "\n") || strstr(appendStr, "\r"))
+	{
+		appendStringInfoCharMacro(buffer, '"');
+
+		for (pChar = appendStr; *pChar; pChar++)
+		{
+			if (*pChar == '"') /* double single quotes */
+				appendStringInfoCharMacro(buffer, *pChar);
+
+			appendStringInfoCharMacro(buffer, *pChar);
+		}
+
+		appendStringInfoCharMacro(buffer, '"');
+	}
+	/* Else just append */
+	else
+	{
+		appendStringInfoString(buffer, appendStr);
+	}
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEventStackItem *stackItem)
+{
+	const char *classname;
+	MemoryContext contextOld;
+
+	/* Check that this event should be logged. */
+	if (!log_check(&stackItem->auditEvent, &classname))
+		return;
+
+	/* Use audit memory context in case something is not freed */
+	contextOld = MemoryContextSwitchTo(stackItem->contextAudit);
+
+	/* Set statement and substatement Ids */
+	if (stackItem->auditEvent.statementId == 0)
+	{
+		/* If nothing has been logged yet then create a new statement Id */
+		if (!statementLogged)
+		{
+			statementTotal++;
+			statementLogged = true;
+		}
+
+		stackItem->auditEvent.statementId = statementTotal;
+		stackItem->auditEvent.substatementId = ++substatementTotal;
+	}
+
+	/* Create the audit string */
+	StringInfoData auditStr;
+	initStringInfo(&auditStr);
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.command);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.objectType);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.objectName);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.commandText);
+
+	/* If parameter logging is turned on and there are parameters to log */
+	if (auditLogBitmap & LOG_PARAMETER &&
+		stackItem->auditEvent.paramList != NULL &&
+		stackItem->auditEvent.paramList->numParams > 0 &&
+		!IsAbortedTransactionBlockState())
+	{
+		ParamListInfo paramList = stackItem->auditEvent.paramList;
+		int paramIdx;
+
+		/* Iterate through all params */
+		for (paramIdx = 0; paramIdx < paramList->numParams; paramIdx++)
+		{
+			ParamExternData *prm = &paramList->params[paramIdx];
+			Oid 			 typeOutput;
+			bool 			 typeIsVarLena;
+			char 			*paramStr;
+
+			/* Add a comma for each param */
+			appendStringInfoCharMacro(&auditStr, ',');
+
+			/* Skip this param if null or if oid is invalid */
+			if (prm->isnull || !OidIsValid(prm->ptype))
+			{
+				continue;
+			}
+
+			/* Output the string */
+			getTypeOutputInfo(prm->ptype, &typeOutput, &typeIsVarLena);
+			paramStr = OidOutputFunctionCall(typeOutput, prm->value);
+
+			append_valid_csv(&auditStr, paramStr);
+			pfree(paramStr);
+		}
+	}
+
+	/* Log the audit string */
+	ereport(LOG,
+		(errmsg("AUDIT: %s,%ld,%ld,%s,%s",
+			stackItem->auditEvent.granted ?
+				AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+			stackItem->auditEvent.statementId,
+			stackItem->auditEvent.substatementId,
+			classname, auditStr.data),
+		 errhidestmt(true)));
+
+	/* Mark the audit event as logged */
+	stackItem->auditEvent.logged = true;
+
+	/* Switch back to the old memory context */
+	MemoryContextSwitchTo(contextOld);
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem	   *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* bms_first_member is destructive, so make a copy before using it. */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for SELECT/DML operations via executor permissions checks.
+ */
+static void
+log_select_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+	bool found = false;
+
+	/* Do not log if this is an internal statement */
+	if (internalStatement)
+		return;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		found = true;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			continue;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_InsertStmt;
+			auditEventStack->auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_UpdateStmt;
+			auditEventStack->auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_DeleteStmt;
+			auditEventStack->auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEventStack->auditEvent.commandTag = T_SelectStmt;
+			auditEventStack->auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEventStack->auditEvent.commandTag = T_Invalid;
+			auditEventStack->auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEventStack->auditEvent.granted = false;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEventStack->auditEvent.objectName = "";
+			auditEventStack->auditEvent.objectType = "";
+
+			log_audit_event(auditEventStack);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEventStack->auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEventStack->auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEventStack->auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEventStack->auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEventStack->auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEventStack->auditEvent.granted)
+		{
+			auditEventStack->auditEvent.logged = false;
+			log_audit_event(auditEventStack);
+		}
+
+		pfree(auditEventStack->auditEvent.objectName);
+	}
+
+	/*
+	 * If no tables were found that means that RangeTbls was empty or all
+	 * relations were in the system schema.  In that case still log a
+	 * session record.
+	 */
+	if (!found)
+	{
+		auditEventStack->auditEvent.granted = false;
+		auditEventStack->auditEvent.logged = false;
+
+		log_audit_event(auditEventStack);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		auditEventStack->auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+	AuditEventStackItem *stackItem;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Push audit event onto the stack */
+	stackItem = stack_push();
+
+	/* Generate the fully-qualified function name. */
+	stackItem->auditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the function call */
+	stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+	stackItem->auditEvent.commandTag = T_DoStmt;
+	stackItem->auditEvent.command = COMMAND_EXECUTE;
+	stackItem->auditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	stackItem->auditEvent.commandText = stackItem->next->auditEvent.commandText;
+
+	log_audit_event(stackItem);
+
+	/* Pop audit event from the stack */
+	stack_pop();
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute */
+		case OAT_FUNCTION_EXECUTE:
+			if (auditLogBitmap & LOG_FUNCTION)
+				log_function_execute(objectId);
+			break;
+
+		/* Log create */
+		case OAT_POST_CREATE:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter */
+		case OAT_POST_ALTER:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop */
+		case OAT_DROP:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessDrop *drop = arg;
+
+				if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
+static ExecutorEnd_hook_type next_ExecutorEnd_hook = NULL;
+
+/*
+ * Hook ExecutorStart to get the query text and basic command type for queries
+ * that do not contain a table so can't be idenitified accurately in
+ * ExecutorCheckPerms.
+ */
+static void
+pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	if (!internalStatement)
+	{
+		/* Allocate the audit event */
+		stackItem = stack_push();
+
+		/* Initialize command */
+		switch (queryDesc->operation)
+		{
+			case CMD_SELECT:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+				stackItem->auditEvent.commandTag = T_SelectStmt;
+				stackItem->auditEvent.command = COMMAND_SELECT;
+				break;
+
+			case CMD_INSERT:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_InsertStmt;
+				stackItem->auditEvent.command = COMMAND_INSERT;
+				break;
+
+			case CMD_UPDATE:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_UpdateStmt;
+				stackItem->auditEvent.command = COMMAND_UPDATE;
+				break;
+
+			case CMD_DELETE:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_DeleteStmt;
+				stackItem->auditEvent.command = COMMAND_DELETE;
+				break;
+
+			default:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+				stackItem->auditEvent.commandTag = T_Invalid;
+				stackItem->auditEvent.command = COMMAND_UNKNOWN;
+				break;
+		}
+
+		/* Initialize the audit event */
+		stackItem->auditEvent.objectName = "";
+		stackItem->auditEvent.objectType = "";
+		stackItem->auditEvent.commandText = queryDesc->sourceText;
+		stackItem->auditEvent.paramList = queryDesc->params;
+	}
+
+	/* Call the previous hook or standard function */
+	if (next_ExecutorStart_hook)
+		next_ExecutorStart_hook(queryDesc, eflags);
+	else
+		standard_ExecutorStart(queryDesc, eflags);
+}
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid;
+
+	/* Get the audit oid if the role exists. */
+	auditOid = get_role_oid(auditRole, true);
+
+	/* Log DML if the audit role is valid or session logging is enabled. */
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_select_dml(auditOid, rangeTabls);
+
+	/* Call the next hook function. */
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ExecutorEnd to pop statement audit event off the stack.
+ */
+static void
+pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
+{
+	/* Call the next hook or standard function */
+	if (next_ExecutorEnd_hook)
+		next_ExecutorEnd_hook(queryDesc);
+	else
+		standard_ExecutorEnd(queryDesc);
+
+	/* Pop the audit event off the stack */
+	if (!internalStatement)
+	{
+		stack_pop();
+	}
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	/* Allocate the audit event */
+	if (!IsAbortedTransactionBlockState())
+	{
+		/* Process top level utility statement */
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			if (auditEventStack != NULL)
+				elog(ERROR, "pg_audit stack is not empty");
+
+			/* Set params */
+			stackItem = stack_push();
+			stackItem->auditEvent.paramList = params;
+		}
+		else
+			stackItem = stack_push();
+
+		stackItem->auditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+		stackItem->auditEvent.commandTag = nodeTag(parsetree);
+		stackItem->auditEvent.command = CreateCommandTag(parsetree);
+		stackItem->auditEvent.objectName = "";
+		stackItem->auditEvent.objectType = "";
+		stackItem->auditEvent.commandText = queryString;
+
+		/*
+		 * If this is a DO block log it before calling the next ProcessUtility
+		 * hook.
+		 */
+		if (auditLogBitmap != 0 &&
+			stackItem->auditEvent.commandTag == T_DoStmt &&
+			!IsAbortedTransactionBlockState())
+		{
+			log_audit_event(stackItem);
+		}
+	}
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Process the audit event if there is one. */
+	if (stackItem != NULL)
+	{
+		/* Log the utility command if logging is on, the command has not already
+		 * been logged by another hook, and the transaction is not aborted. */
+		if (auditLogBitmap != 0 && !stackItem->auditEvent.logged &&
+			!IsAbortedTransactionBlockState())
+			log_audit_event(stackItem);
+
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			while (auditEventStack != NULL)
+				stack_pop();
+		}
+		else
+			stack_pop();
+	}
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState() &&
+		auditLogBitmap & (LOG_DDL | LOG_FUNCTION))
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * Event trigger functions
+ */
+
+/*
+ * Supply additional data for (non drop) statements that have event trigger
+ * support and can be deparsed.
+ */
+Datum
+pg_audit_ddl_command_end(PG_FUNCTION_ARGS)
+{
+	/* Continue only if session logging is enabled */
+	if (auditLogBitmap != LOG_DDL)
+	{
+		EventTriggerData *eventData;
+		int				  result, row;
+		TupleDesc		  spiTupDesc;
+		const char		 *query;
+		MemoryContext 	  contextQuery;
+		MemoryContext 	  contextOld;
+
+		/* This is an internal statement - do not log it */
+		internalStatement = true;
+
+		/* Make sure the fuction was fired as a trigger */
+		if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+			elog(ERROR, "not fired by event trigger manager");
+
+		/* Switch memory context */
+		contextQuery = AllocSetContextCreate(
+						CurrentMemoryContext,
+						"pgaudit_func_ddl_command_end temporary context",
+						ALLOCSET_DEFAULT_MINSIZE,
+						ALLOCSET_DEFAULT_INITSIZE,
+						ALLOCSET_DEFAULT_MAXSIZE);
+		contextOld = MemoryContextSwitchTo(contextQuery);
+
+		/* Get information about triggered events */
+		eventData = (EventTriggerData *) fcinfo->context;
+
+		/* Return objects affected by the (non drop) DDL statement */
+		query = "SELECT classid, objid, objsubid, UPPER(object_type), schema,\n"
+				"       identity, command\n"
+				"  FROM pg_event_trigger_get_creation_commands()";
+
+		/* Attempt to connect */
+		result = SPI_connect();
+
+		if (result < 0)
+			elog(ERROR, "pg_audit_ddl_command_end: SPI_connect returned %d",
+						result);
+
+		/* Execute the query */
+		result = SPI_execute(query, true, 0);
+
+		if (result != SPI_OK_SELECT)
+			elog(ERROR, "pg_audit_ddl_command_end: SPI_execute returned %d",
+						result);
+
+		/* Iterate returned rows */
+		spiTupDesc = SPI_tuptable->tupdesc;
+
+		for (row = 0; row < SPI_processed; row++)
+		{
+			HeapTuple  spiTuple;
+			bool	   isNull;
+
+			spiTuple = SPI_tuptable->vals[row];
+
+			/* Supply addition data to current audit event */
+			auditEventStack->auditEvent.logStmtLevel =
+				GetCommandLogLevel(eventData->parsetree);
+			auditEventStack->auditEvent.commandTag =
+				nodeTag(eventData->parsetree);
+			auditEventStack->auditEvent.command =
+				CreateCommandTag(eventData->parsetree);
+			auditEventStack->auditEvent.objectName =
+				SPI_getvalue(spiTuple, spiTupDesc, 6);
+			auditEventStack->auditEvent.objectType =
+				SPI_getvalue(spiTuple, spiTupDesc, 4);
+			auditEventStack->auditEvent.commandText =
+				TextDatumGetCString(
+					DirectFunctionCall1(pg_event_trigger_expand_command,
+										SPI_getbinval(spiTuple, spiTupDesc,
+													  7, &isNull)));
+
+			/* Log the audit event */
+			log_audit_event(auditEventStack);
+		}
+
+		/* Complete the query */
+		SPI_finish();
+
+		/* Switch to the old memory context */
+		MemoryContextSwitchTo(contextOld);
+		MemoryContextDelete(contextQuery);
+
+		/* No longer in an internal statement */
+		internalStatement = false;
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Supply additional data for drop statements that have event trigger support.
+ */
+Datum
+pg_audit_sql_drop(PG_FUNCTION_ARGS)
+{
+	if (auditLogBitmap & LOG_DDL)
+	{
+		EventTriggerData *eventData;
+		int				  result, row;
+		TupleDesc		  spiTupDesc;
+		const char		 *query;
+		MemoryContext 	  contextQuery;
+		MemoryContext 	  contextOld;
+
+		/* This is an internal statement - do not log it */
+		internalStatement = true;
+
+		/* Make sure the fuction was fired as a trigger */
+		if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+			elog(ERROR, "not fired by event trigger manager");
+
+		/* Switch memory context */
+		contextQuery = AllocSetContextCreate(
+						CurrentMemoryContext,
+						"pgaudit_func_ddl_command_end temporary context",
+						ALLOCSET_DEFAULT_MINSIZE,
+						ALLOCSET_DEFAULT_INITSIZE,
+						ALLOCSET_DEFAULT_MAXSIZE);
+		contextOld = MemoryContextSwitchTo(contextQuery);
+
+		/* Get information about triggered events */
+		eventData = (EventTriggerData *) fcinfo->context;
+
+		/* Return objects affected by the drop statement */
+		query = "SELECT classid, objid, objsubid, UPPER(object_type),\n"
+				"       schema_name, object_name, object_identity\n"
+				"  FROM pg_event_trigger_dropped_objects()";
+
+		/* Attempt to connect */
+		result = SPI_connect();
+
+		if (result < 0)
+			elog(ERROR, "pg_audit_ddl_drop: SPI_connect returned %d",
+						result);
+
+		/* Execute the query */
+		result = SPI_execute(query, true, 0);
+
+		if (result != SPI_OK_SELECT)
+			elog(ERROR, "pg_audit_ddl_drop: SPI_execute returned %d",
+						result);
+
+		/* Iterate returned rows */
+		spiTupDesc = SPI_tuptable->tupdesc;
+
+		for (row = 0; row < SPI_processed; row++)
+		{
+			HeapTuple  spiTuple;
+			char *schemaName;
+
+			spiTuple = SPI_tuptable->vals[row];
+
+			auditEventStack->auditEvent.objectType =
+				SPI_getvalue(spiTuple, spiTupDesc, 4);
+			schemaName = SPI_getvalue(spiTuple, spiTupDesc, 5);
+
+			if (!(pg_strcasecmp(auditEventStack->auditEvent.objectType,
+							"TYPE") == 0 ||
+				  pg_strcasecmp(schemaName, "pg_toast") == 0))
+			{
+				auditEventStack->auditEvent.objectName =
+						SPI_getvalue(spiTuple, spiTupDesc, 7);
+
+				log_audit_event(auditEventStack);
+			}
+		}
+
+		/* Complete the query */
+		SPI_finish();
+
+		/* Switch to the old memory context */
+		MemoryContextSwitchTo(contextOld);
+		MemoryContextDelete(contextQuery);
+
+		/* No longer in an internal statement */
+		internalStatement = false;
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pg_audit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_PARAMETER) == 0)
+			class = LOG_PARAMETER;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+			(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+			errmsg("pg_audit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pg_audit.role = "audit"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pg_audit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pg_audit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pg_audit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorStart_hook = ExecutorStart_hook;
+	ExecutorStart_hook = pgaudit_ExecutorStart_hook;
+
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ExecutorEnd_hook = ExecutorEnd_hook;
+	ExecutorEnd_hook = pgaudit_ExecutorEnd_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..6730c68
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pg_audit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..f5cbde2
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1412 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pg_audit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_PARAMETER => 'PARAMETER',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE					=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE			=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION			=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION		=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE			=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE				=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET			=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE				=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_COLUMN		=> 'ALTER TABLE COLUMN',
+	COMMAND_ALTER_TABLE_INDEX		=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN					=> 'BEGIN',
+	COMMAND_CLOSE					=> 'CLOSE CURSOR',
+	COMMAND_COMMIT					=> 'COMMIT',
+	COMMAND_COPY					=> 'COPY',
+	COMMAND_COPY_TO					=> 'COPY TO',
+	COMMAND_COPY_FROM				=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE		=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION		=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION		=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE			=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX			=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE				=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR			=> 'DECLARE CURSOR',
+	COMMAND_DO						=> 'DO',
+	COMMAND_DISCARD_ALL				=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION			=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE				=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA			=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE			=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS			=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE			=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA				=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE				=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_CONSTRAINT	=> 'DROP TABLE CONSTRAINT',
+	COMMAND_DROP_TABLE_INDEX		=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TOAST		=> 'DROP TABLE TOAST',
+	COMMAND_DROP_TABLE_TYPE			=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE					=> 'EXECUTE',
+	COMMAND_EXECUTE_READ			=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE			=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION		=> 'EXECUTE FUNCTION',
+	COMMAND_EXPLAIN					=> 'EXPLAIN',
+	COMMAND_FETCH					=> 'FETCH',
+	COMMAND_GRANT					=> 'GRANT',
+	COMMAND_INSERT					=> 'INSERT',
+	# COMMAND_PARAMETER				=> 'PARAMETER',
+	# COMMAND_PARAMETER_READ			=> 'PARAMETER_READ',
+	# COMMAND_PARAMETER_WRITE			=> 'PARAMETER_WRITE',
+	COMMAND_PREPARE					=> 'PREPARE',
+	COMMAND_PREPARE_READ			=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE			=> 'PREPARE WRITE',
+	COMMAND_REVOKE					=> 'REVOKE',
+	COMMAND_SELECT					=> 'SELECT',
+	COMMAND_SET						=> 'SET',
+	COMMAND_UPDATE					=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE					=> 'TYPE',
+	TYPE_NONE				=> '',
+
+	TYPE_AGGREGATE			=> 'AGGREGATE',
+	TYPE_COLLATION			=> 'COLLATION',
+	TYPE_CONVERSION			=> 'CONVERSION',
+	TYPE_SCHEMA				=> 'SCHEMA',
+	TYPE_FUNCTION			=> 'FUNCTION',
+	TYPE_INDEX				=> 'INDEX',
+	TYPE_TABLE				=> 'TABLE',
+	TYPE_TABLE_COLUMN		=> 'TABLE COLUMN',
+	TYPE_TABLE_CONSTRAINT	=> 'TABLE CONSTRAINT',
+	TYPE_TABLE_TOAST		=> 'TABLE TOAST',
+	TYPE_TYPE				=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pg_audit.log GUCS
+my %oAuditGrantHash;			# Hash to store pg_audit grants
+
+my $strCurrentAuditLog;		# pg_audit.log setting was Postgres was started with
+my $strTemporaryAuditLog;	# pg_audit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_AGGREGATE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_COLLATION},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_CONVERSION},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_COLUMN => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_COLUMN, &COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_AGGREGATE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_CONVERSION},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_COLLATION},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_FUNCTION},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_SCHEMA},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_CONSTRAINT => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_CONSTRAINT, &COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TOAST => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_TOAST, &COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXPLAIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+				   " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+				   " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+				   " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+	my $strParameter = shift;
+	my $bExpectError = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		eval
+		{
+			PgExecuteOnly($strSql);
+		};
+
+		if ($@ && !$bExpectError)
+		{
+			confess $@;
+		}
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $strParameter, $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# QuoteCSV
+################################################################################
+sub QuoteCSV
+{
+	my $strCSV = shift;
+
+	if (defined($strCSV) &&
+		(index($strCSV, ',') >= 0 || index($strCSV, '"') > 0 ||
+		 index($strCSV, "\n") > 0 || index($strCSV, "\r") >= 0))
+	{
+		$strCSV =~ s/"/""/g;
+		$strCSV = "\"${strCSV}\"";
+	}
+
+	return $strCSV;
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $strParameter = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Quote SQL if needs to be quoted
+	$strSql = QuoteCSV($strSql);
+
+	if (defined($strParameter))
+	{
+		$strSql .= ",${strParameter}";
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = QuoteCSV($oData);
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = QuoteCSV(${$oTableHash}{&NAME});
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+	$strLogActual =~ s/SESSION,[0-9]+,[0-9]+,/SESSION,/g;
+	$strLogActual =~ s/OBJECT,[0-9]+,[0-9]+,/OBJECT,/g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   " -c log_statement=all" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pg_audit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pg_audit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pg_audit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "GRANT " .
+								(defined($strColumn) ?
+									lc(${strPrivilege}) ." (${strColumn})" :
+									uc(${strPrivilege})) .
+								" ON TABLE ${strObject} TO ${strRole} ");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "REVOKE  " . uc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " ON TABLE ${strObject} FROM ${strRole} ");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable;	   # Store table info for select, insert, update, delete
+my $strSql;		# Hold Sql commands
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+
+$strSql = 'CREATE  TABLE  public.test (id pg_catalog.int4   )' .
+		  '  WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+$strSql = 'drop table test';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			&COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Create some email friendly tests.  These first tests are session logging only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+
+PgSetUser('user1');
+
+$strSql = 'CREATE  TABLE  public.account (id pg_catalog.int4   ,' .
+		  ' name pg_catalog.text   COLLATE pg_catalog."default", ' .
+		  'password pg_catalog.text   COLLATE pg_catalog."default", '.
+		  'description pg_catalog.text   COLLATE pg_catalog."default")  '.
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pg_audit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+				'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pg_audit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+
+$strSql = 'CREATE SCHEMA  test ';
+PgLogExecute(COMMAND_CREATE_SCHEMA, $strSql, 'test');
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+
+$strSql = 'CREATE  TABLE  test.pg_class  WITH (oids=OFF)   AS SELECT relname,' .
+		  ' relnamespace, reltype, reloftype, relowner, relam, relfilenode, ' .
+		  'reltablespace, relpages, reltuples, relallvisible, reltoastrelid, ' .
+		  'relhasindex, relisshared, relpersistence, relkind, relnatts, ' .
+		  'relchecks, relhasoids, relhaspkey, relhasrules, relhastriggers, ' .
+		  'relhassubclass, relrowsecurity, relispopulated, relreplident, ' .
+		  'relfrozenxid, relminmxid, relacl, reloptions ' .
+		  'FROM pg_catalog.pg_class ';
+PgLogExecute(COMMAND_INSERT, $strSql, undef, true, false);
+PgLogExecute(COMMAND_CREATE_TABLE_AS, $strSql, 'test.pg_class', false, true);
+
+$strSql = "COPY test.pg_class from '" . abs_path($strTestPath) . "/class.out'";
+PgLogExecute(COMMAND_INSERT, $strSql);
+#PgLogExecute(COMMAND_COPY_FROM, $strSql, undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+			 'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+$strSql = 'CREATE  TABLE  test.test_insert (id pg_catalog.int4   )  ' .
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'test.test_insert');
+
+$strSql = 'PREPARE pgclassstmt (oid) as insert into test.test_insert (id) ' .
+		  'values ($1)';
+PgLogExecute(COMMAND_PREPARE_WRITE, $strSql);
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, "1");
+
+$strSql = 'EXECUTE pgclassstmt (1)';
+PgLogExecute(COMMAND_EXECUTE_WRITE, $strSql, undef, true, true);
+
+# Create a table with a primary key
+$strSql = 'CREATE  TABLE  public.test (id pg_catalog.int4   , ' .
+		  'name pg_catalog.text   COLLATE pg_catalog."default", description ' .
+		  'pg_catalog.text   COLLATE pg_catalog."default", CONSTRAINT ' .
+		  'test_pkey PRIMARY KEY (id))  WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_INDEX, $strSql, 'public.test_pkey', true, false);
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test', false, true);
+
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+$strSql = 'GRANT SELECT ON TABLE public.test TO PUBLIC ';
+PgLogExecute(COMMAND_GRANT, $strSql);
+
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+
+$strSql = 'GRANT select (name) ON TABLE public.test TO PUBLIC ';
+PgLogExecute(COMMAND_GRANT, $strSql);
+
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Try a select that does not reference any tables
+PgLogExecute(COMMAND_SELECT, 'select 1, current_timestamp');
+
+# Now try the same in a do block
+$strSql = 'do $$ declare test int; begin select 1 into test; end $$';
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'select 1';
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, true);
+
+# Insert some data into test and try a loop in a do block
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (1)');
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (2)');
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (3)');
+
+$strSql = 'do $$ ' .
+		  'declare ' .
+		  '	result record;' .
+		  'begin ' .
+		  '	for result in select id from test loop ' .
+		  '		insert into test (id) values (result.id + 100); ' .
+		  '	end loop; ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'select id from test';
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false);
+
+$strSql = 'insert into test (id) values (result.id + 100)';
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+# Test EXECUTE with bind
+$strSql = "select * from test where id = ?";
+my $hStatement = $hDb->prepare($strSql);
+
+$strSql = "select * from test where id = \$1";
+$hStatement->bind_param(1, 101);
+$hStatement->execute();
+
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false, undef, "101");
+
+$hStatement->bind_param(1, 103);
+$hStatement->execute();
+
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false, undef, "103");
+
+$hStatement->finish();
+
+# Now try some DDL in a do block
+$strSql = 'do $$ ' .
+		  'begin ' .
+		  '	create table test_block (id int); ' .
+		  '	drop table test_block; ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'CREATE  TABLE  public.test_block (id pg_catalog.int4   )  ' .
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test_block', false, false);
+
+$strSql = 'drop table test_block';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'public.test_block', false, false);
+
+# Generate an error in a do block and make sure the stack gets cleaned up
+$strSql = 'do $$ ' .
+		  'begin ' .
+		  '	create table bobus.test_block (id int); ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, undef, undef, undef, undef, true);
+# PgLogExecute(COMMAND_SELECT, 'select 1');
+# exit 0;
+
+# Try explain
+PgLogExecute(COMMAND_SELECT, 'explain select 1', undef, true, false);
+PgLogExecute(COMMAND_EXPLAIN, 'explain select 1', undef, false, true);
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+# Test alter and drop table statements
+$strSql = 'ALTER TABLE public.test DROP COLUMN description ';
+PgLogExecute(COMMAND_ALTER_TABLE_COLUMN,
+			 $strSql, 'public.test.description', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 $strSql, 'public.test', false, true);
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+$strSql = 'ALTER TABLE  public.test RENAME TO test2';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'public.test2');
+
+$strSql = 'ALTER TABLE public.test2 SET SCHEMA test';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'test.test2');
+
+$strSql = 'ALTER TABLE test.test2 ADD COLUMN description pg_catalog.text   ' .
+		  'COLLATE pg_catalog."default"';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'test.test2');
+
+$strSql = 'ALTER TABLE test.test2 DROP COLUMN description ';
+PgLogExecute(COMMAND_ALTER_TABLE_COLUMN, $strSql,
+			 'test.test2.description', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql,
+			 'test.test2', false, true);
+
+$strSql = 'drop table test.test2';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'test.test2', true, false);
+PgLogExecute(COMMAND_DROP_TABLE_CONSTRAINT, $strSql, 'test_pkey on test.test2',
+			 false, false);
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, $strSql, 'test.test_pkey', false, true);
+
+$strSql = "CREATE  FUNCTION public.int_add(IN a pg_catalog.int4 , IN b " .
+		  "pg_catalog.int4 ) RETURNS  pg_catalog.int4 LANGUAGE plpgsql  " .
+		  "VOLATILE  CALLED ON NULL INPUT SECURITY INVOKER COST 100   AS '" .
+		  " begin return a + b; end '";
+PgLogExecute(COMMAND_CREATE_FUNCTION, $strSql,
+			 'public.int_add(integer,integer)');
+PgLogExecute(COMMAND_SELECT, "select int_add(1, 1)",
+							 undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add', false, true);
+
+$strSql = "CREATE AGGREGATE public.sum_test(  pg_catalog.int4) " .
+		  "(SFUNC=public.int_add, STYPE=pg_catalog.int4, INITCOND='0')";
+PgLogExecute(COMMAND_CREATE_AGGREGATE, $strSql, 'public.sum_test(integer)');
+
+# There's a bug here in deparse:
+$strSql = "ALTER AGGREGATE public.sum_test(integer) RENAME TO sum_test2";
+PgLogExecute(COMMAND_ALTER_AGGREGATE, $strSql, 'public.sum_test2(integer)');
+
+$strSql = "CREATE COLLATION public.collation_test (LC_COLLATE = 'de_DE', " .
+		  "LC_CTYPE = 'de_DE')";
+PgLogExecute(COMMAND_CREATE_COLLATION, $strSql, 'public.collation_test');
+
+$strSql =  "ALTER COLLATION public.collation_test RENAME TO collation_test2";
+PgLogExecute(COMMAND_ALTER_COLLATION, $strSql, 'public.collation_test2');
+
+$strSql = "CREATE  CONVERSION public.conversion_test FOR 'SQL_ASCII' " .
+		  "TO 'MULE_INTERNAL' FROM pg_catalog.ascii_to_mic";
+PgLogExecute(COMMAND_CREATE_CONVERSION, $strSql, 'public.conversion_test');
+
+$strSql = "ALTER CONVERSION public.conversion_test RENAME TO conversion_test2";
+PgLogExecute(COMMAND_ALTER_CONVERSION, $strSql, 'public.conversion_test2');
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 89fff77..6b0b407 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -125,6 +125,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..f9152cd
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,335 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pgaudit" xreflabel="pgaudit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pgaudit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pg_audit.log</literal>
+        GUC. There are six classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>PARAMETER</literal> - Parameters that were passed for the statement.  Parameters immediately follow the statement text. 
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pg_audit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pg_audit.log = 'all, -misc'
+          </programlisting>
+      </para>
+      
+      <para>
+      Note that <literal>pg_audit.log</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pg_audit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pg_audit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+
+      <para>
+      Note that <literal>pg_audit.role</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Authors</title>
+
+    <para>
+      Abhijit Menon-Sen <email>[email protected]</email>, Ian Barwick <email>[email protected]</email>, and David Steele <email>[email protected]</email>.
+    </para>
+  </sect2>
+</sect1>


Attachments:

  [text/plain] pg_audit-v4.patch (101.2K, ../../[email protected]/2-pg_audit-v4.patch)
  download | inline diff:
diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..9d9ee83
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,22 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
+
+CREATE FUNCTION pg_audit_ddl_command_end()
+	RETURNS event_trigger
+	LANGUAGE C
+	AS 'MODULE_PATHNAME', 'pg_audit_ddl_command_end';
+
+CREATE EVENT TRIGGER pg_audit_ddl_command_end
+	ON ddl_command_end
+	EXECUTE PROCEDURE pg_audit_ddl_command_end();
+
+CREATE FUNCTION pg_audit_sql_drop()
+	RETURNS event_trigger
+	LANGUAGE C
+	AS 'MODULE_PATHNAME', 'pg_audit_sql_drop';
+
+CREATE EVENT TRIGGER pg_audit_sql_drop
+	ON sql_drop
+	EXECUTE PROCEDURE pg_audit_sql_drop();
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..6c24650
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1716 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements (See
+ * pg_audit.sgml for details).
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_audit/pg_audit.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * Event trigger prototypes
+ */
+Datum pg_audit_ddl_command_end(PG_FUNCTION_ARGS);
+Datum pg_audit_sql_drop(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(pg_audit_ddl_command_end);
+PG_FUNCTION_INFO_V1(pg_audit_sql_drop);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String constants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC			"MISC"
+#define CLASS_PARAMETER		"PARAMETER"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 1),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 2),
+
+	/* Statements not covered by another class */
+	LOG_MISC = (1 << 3),
+
+	/* Function execution */
+	LOG_PARAMETER = (1 << 4),
+
+	/* SELECT */
+	LOG_READ = (1 << 5),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 6),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String constants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If a statement affects multiple objects multiple AuditEvents must be
+ * created to represent it.
+ */
+typedef struct
+{
+	int64 statementId;
+	int64 substatementId;
+
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	ParamListInfo paramList;
+
+	bool granted;
+	bool logged;
+} AuditEvent;
+
+/*
+ * A simple FIFO queue to keep track of the current stack of audit events.
+ */
+typedef struct AuditEventStackItem
+{
+	struct AuditEventStackItem *next;
+
+	AuditEvent auditEvent;
+
+	MemoryContext contextAudit;
+	MemoryContextCallback contextCallback;
+} AuditEventStackItem;
+
+AuditEventStackItem *auditEventStack = NULL;
+
+/*
+ * Track when an internal statement is running so it is not logged
+ */
+static bool internalStatement = false;
+
+/*
+ * Track running total for statements and substatements and whether or not
+ * anything has been logged since this statement began.
+ */
+static uint64 statementTotal = 0;
+static uint64 substatementTotal = 0;
+
+static bool statementLogged = false;
+
+/*
+ * Stack functions
+ *
+ * Audit events can go down to multiple levels so a stack is maintained to keep
+ * track of them.
+ */
+
+/*
+ * Respond to callbacks registered with MemoryContextRegisterResetCallback().
+ * Removes the event(s) off the stack that have become obsolete once the
+ * MemoryContext has been freed.  The callback should always be freeing the top
+ * of the stack, but the code is tolerant of out-of-order callbacks.
+ */
+static void
+stack_free(void *stackFree)
+{
+	AuditEventStackItem *nextItem = auditEventStack;
+
+	/* Only process if the stack contains items */
+	while (nextItem != NULL)
+	{
+		/* Check if this item matches the item to be freed */
+		if (nextItem == (AuditEventStackItem *)stackFree)
+		{
+			/* Move top of stack the the item after the freed item */
+			auditEventStack = nextItem->next;
+
+			/* If the stack is not empty */
+			if (auditEventStack == NULL)
+			{
+				/* Reset internal statement in case of error */
+				internalStatement = false;
+
+				/* Reset sub statement total */
+				substatementTotal = 0;
+
+				/* Reset statement logged flag total */
+				statementLogged = false;
+			}
+
+			return;
+		}
+
+		/* Still looking, test the next item */
+		nextItem = nextItem->next;
+	}
+}
+
+/*
+ * Push a new audit event onto the stack and create a new memory context to
+ * store it.
+ */
+static AuditEventStackItem *
+stack_push()
+{
+	MemoryContext contextAudit;
+	MemoryContext contextOld;
+	AuditEventStackItem *stackItem;
+
+	/* Create a new memory context */
+	contextAudit = AllocSetContextCreate(CurrentMemoryContext,
+										 "pg_audit stack context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+	contextOld = MemoryContextSwitchTo(contextAudit);
+
+	/* Allocate the stack item */
+	stackItem = palloc0(sizeof(AuditEventStackItem));
+
+	/* Store memory contexts */
+	stackItem->contextAudit = contextAudit;
+
+	/* If item already on stack then push it down */
+	if (auditEventStack != NULL)
+		stackItem->next = auditEventStack;
+	else
+		stackItem->next = NULL;
+
+	/*
+	 * Setup a callback in case an error happens.  stack_free() will truncate
+	 * the stack at this item.
+	 */
+	stackItem->contextCallback.func = stack_free;
+	stackItem->contextCallback.arg = (void *)stackItem;
+	MemoryContextRegisterResetCallback(contextAudit,
+									   &stackItem->contextCallback);
+
+	/* Push item on the stack */
+	auditEventStack = stackItem;
+
+	/* Return to the old memory context */
+	MemoryContextSwitchTo(contextOld);
+
+	/* Return the stack item */
+	return stackItem;
+}
+
+/*
+ * Pop an audit event from the stack by deleting the memory context that
+ * contains it.  The callback to stack_free() does the actual pop.
+ */
+static void
+stack_pop()
+{
+	/* Error if the stack is already empty */
+	if (auditEventStack == NULL)
+		elog(ERROR, "pg_audit stack is already empty");
+
+	/* Switch the old memory context and delete the audit context */
+	MemoryContextDelete(auditEventStack->contextAudit);
+}
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Appends a properly quoted CSV field to StringInfo.
+ */
+static void
+append_valid_csv(StringInfoData *buffer, const char *appendStr)
+{
+	const char *pChar;
+
+	/*
+	 * If the append string is null then return.  NULL fields are not quoted
+	 * in CSV
+	 */
+	if (appendStr == NULL)
+		return;
+
+	/* Only format for CSV if appendStr contains: ", comma, \n, \r */
+	if (strstr(appendStr, ",") || strstr(appendStr, "\"") ||
+		strstr(appendStr, "\n") || strstr(appendStr, "\r"))
+	{
+		appendStringInfoCharMacro(buffer, '"');
+
+		for (pChar = appendStr; *pChar; pChar++)
+		{
+			if (*pChar == '"') /* double single quotes */
+				appendStringInfoCharMacro(buffer, *pChar);
+
+			appendStringInfoCharMacro(buffer, *pChar);
+		}
+
+		appendStringInfoCharMacro(buffer, '"');
+	}
+	/* Else just append */
+	else
+	{
+		appendStringInfoString(buffer, appendStr);
+	}
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEventStackItem *stackItem)
+{
+	const char *classname;
+	MemoryContext contextOld;
+
+	/* Check that this event should be logged. */
+	if (!log_check(&stackItem->auditEvent, &classname))
+		return;
+
+	/* Use audit memory context in case something is not freed */
+	contextOld = MemoryContextSwitchTo(stackItem->contextAudit);
+
+	/* Set statement and substatement Ids */
+	if (stackItem->auditEvent.statementId == 0)
+	{
+		/* If nothing has been logged yet then create a new statement Id */
+		if (!statementLogged)
+		{
+			statementTotal++;
+			statementLogged = true;
+		}
+
+		stackItem->auditEvent.statementId = statementTotal;
+		stackItem->auditEvent.substatementId = ++substatementTotal;
+	}
+
+	/* Create the audit string */
+	StringInfoData auditStr;
+	initStringInfo(&auditStr);
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.command);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.objectType);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.objectName);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.commandText);
+
+	/* If parameter logging is turned on and there are parameters to log */
+	if (auditLogBitmap & LOG_PARAMETER &&
+		stackItem->auditEvent.paramList != NULL &&
+		stackItem->auditEvent.paramList->numParams > 0 &&
+		!IsAbortedTransactionBlockState())
+	{
+		ParamListInfo paramList = stackItem->auditEvent.paramList;
+		int paramIdx;
+
+		/* Iterate through all params */
+		for (paramIdx = 0; paramIdx < paramList->numParams; paramIdx++)
+		{
+			ParamExternData *prm = &paramList->params[paramIdx];
+			Oid 			 typeOutput;
+			bool 			 typeIsVarLena;
+			char 			*paramStr;
+
+			/* Add a comma for each param */
+			appendStringInfoCharMacro(&auditStr, ',');
+
+			/* Skip this param if null or if oid is invalid */
+			if (prm->isnull || !OidIsValid(prm->ptype))
+			{
+				continue;
+			}
+
+			/* Output the string */
+			getTypeOutputInfo(prm->ptype, &typeOutput, &typeIsVarLena);
+			paramStr = OidOutputFunctionCall(typeOutput, prm->value);
+
+			append_valid_csv(&auditStr, paramStr);
+			pfree(paramStr);
+		}
+	}
+
+	/* Log the audit string */
+	ereport(LOG,
+		(errmsg("AUDIT: %s,%ld,%ld,%s,%s",
+			stackItem->auditEvent.granted ?
+				AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+			stackItem->auditEvent.statementId,
+			stackItem->auditEvent.substatementId,
+			classname, auditStr.data),
+		 errhidestmt(true)));
+
+	/* Mark the audit event as logged */
+	stackItem->auditEvent.logged = true;
+
+	/* Switch back to the old memory context */
+	MemoryContextSwitchTo(contextOld);
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem	   *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* bms_first_member is destructive, so make a copy before using it. */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for SELECT/DML operations via executor permissions checks.
+ */
+static void
+log_select_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+	bool found = false;
+
+	/* Do not log if this is an internal statement */
+	if (internalStatement)
+		return;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		found = true;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			continue;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_InsertStmt;
+			auditEventStack->auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_UpdateStmt;
+			auditEventStack->auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_DeleteStmt;
+			auditEventStack->auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEventStack->auditEvent.commandTag = T_SelectStmt;
+			auditEventStack->auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEventStack->auditEvent.commandTag = T_Invalid;
+			auditEventStack->auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEventStack->auditEvent.granted = false;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEventStack->auditEvent.objectName = "";
+			auditEventStack->auditEvent.objectType = "";
+
+			log_audit_event(auditEventStack);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEventStack->auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEventStack->auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEventStack->auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEventStack->auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEventStack->auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEventStack->auditEvent.granted)
+		{
+			auditEventStack->auditEvent.logged = false;
+			log_audit_event(auditEventStack);
+		}
+
+		pfree(auditEventStack->auditEvent.objectName);
+	}
+
+	/*
+	 * If no tables were found that means that RangeTbls was empty or all
+	 * relations were in the system schema.  In that case still log a
+	 * session record.
+	 */
+	if (!found)
+	{
+		auditEventStack->auditEvent.granted = false;
+		auditEventStack->auditEvent.logged = false;
+
+		log_audit_event(auditEventStack);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		auditEventStack->auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+	AuditEventStackItem *stackItem;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Push audit event onto the stack */
+	stackItem = stack_push();
+
+	/* Generate the fully-qualified function name. */
+	stackItem->auditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the function call */
+	stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+	stackItem->auditEvent.commandTag = T_DoStmt;
+	stackItem->auditEvent.command = COMMAND_EXECUTE;
+	stackItem->auditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	stackItem->auditEvent.commandText = stackItem->next->auditEvent.commandText;
+
+	log_audit_event(stackItem);
+
+	/* Pop audit event from the stack */
+	stack_pop();
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute */
+		case OAT_FUNCTION_EXECUTE:
+			if (auditLogBitmap & LOG_FUNCTION)
+				log_function_execute(objectId);
+			break;
+
+		/* Log create */
+		case OAT_POST_CREATE:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter */
+		case OAT_POST_ALTER:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop */
+		case OAT_DROP:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessDrop *drop = arg;
+
+				if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
+static ExecutorEnd_hook_type next_ExecutorEnd_hook = NULL;
+
+/*
+ * Hook ExecutorStart to get the query text and basic command type for queries
+ * that do not contain a table so can't be idenitified accurately in
+ * ExecutorCheckPerms.
+ */
+static void
+pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	if (!internalStatement)
+	{
+		/* Allocate the audit event */
+		stackItem = stack_push();
+
+		/* Initialize command */
+		switch (queryDesc->operation)
+		{
+			case CMD_SELECT:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+				stackItem->auditEvent.commandTag = T_SelectStmt;
+				stackItem->auditEvent.command = COMMAND_SELECT;
+				break;
+
+			case CMD_INSERT:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_InsertStmt;
+				stackItem->auditEvent.command = COMMAND_INSERT;
+				break;
+
+			case CMD_UPDATE:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_UpdateStmt;
+				stackItem->auditEvent.command = COMMAND_UPDATE;
+				break;
+
+			case CMD_DELETE:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_DeleteStmt;
+				stackItem->auditEvent.command = COMMAND_DELETE;
+				break;
+
+			default:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+				stackItem->auditEvent.commandTag = T_Invalid;
+				stackItem->auditEvent.command = COMMAND_UNKNOWN;
+				break;
+		}
+
+		/* Initialize the audit event */
+		stackItem->auditEvent.objectName = "";
+		stackItem->auditEvent.objectType = "";
+		stackItem->auditEvent.commandText = queryDesc->sourceText;
+		stackItem->auditEvent.paramList = queryDesc->params;
+	}
+
+	/* Call the previous hook or standard function */
+	if (next_ExecutorStart_hook)
+		next_ExecutorStart_hook(queryDesc, eflags);
+	else
+		standard_ExecutorStart(queryDesc, eflags);
+}
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid;
+
+	/* Get the audit oid if the role exists. */
+	auditOid = get_role_oid(auditRole, true);
+
+	/* Log DML if the audit role is valid or session logging is enabled. */
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_select_dml(auditOid, rangeTabls);
+
+	/* Call the next hook function. */
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ExecutorEnd to pop statement audit event off the stack.
+ */
+static void
+pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
+{
+	/* Call the next hook or standard function */
+	if (next_ExecutorEnd_hook)
+		next_ExecutorEnd_hook(queryDesc);
+	else
+		standard_ExecutorEnd(queryDesc);
+
+	/* Pop the audit event off the stack */
+	if (!internalStatement)
+	{
+		stack_pop();
+	}
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	/* Allocate the audit event */
+	if (!IsAbortedTransactionBlockState())
+	{
+		/* Process top level utility statement */
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			if (auditEventStack != NULL)
+				elog(ERROR, "pg_audit stack is not empty");
+
+			/* Set params */
+			stackItem = stack_push();
+			stackItem->auditEvent.paramList = params;
+		}
+		else
+			stackItem = stack_push();
+
+		stackItem->auditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+		stackItem->auditEvent.commandTag = nodeTag(parsetree);
+		stackItem->auditEvent.command = CreateCommandTag(parsetree);
+		stackItem->auditEvent.objectName = "";
+		stackItem->auditEvent.objectType = "";
+		stackItem->auditEvent.commandText = queryString;
+
+		/*
+		 * If this is a DO block log it before calling the next ProcessUtility
+		 * hook.
+		 */
+		if (auditLogBitmap != 0 &&
+			stackItem->auditEvent.commandTag == T_DoStmt &&
+			!IsAbortedTransactionBlockState())
+		{
+			log_audit_event(stackItem);
+		}
+	}
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Process the audit event if there is one. */
+	if (stackItem != NULL)
+	{
+		/* Log the utility command if logging is on, the command has not already
+		 * been logged by another hook, and the transaction is not aborted. */
+		if (auditLogBitmap != 0 && !stackItem->auditEvent.logged &&
+			!IsAbortedTransactionBlockState())
+			log_audit_event(stackItem);
+
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			while (auditEventStack != NULL)
+				stack_pop();
+		}
+		else
+			stack_pop();
+	}
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState() &&
+		auditLogBitmap & (LOG_DDL | LOG_FUNCTION))
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * Event trigger functions
+ */
+
+/*
+ * Supply additional data for (non drop) statements that have event trigger
+ * support and can be deparsed.
+ */
+Datum
+pg_audit_ddl_command_end(PG_FUNCTION_ARGS)
+{
+	/* Continue only if session logging is enabled */
+	if (auditLogBitmap != LOG_DDL)
+	{
+		EventTriggerData *eventData;
+		int				  result, row;
+		TupleDesc		  spiTupDesc;
+		const char		 *query;
+		MemoryContext 	  contextQuery;
+		MemoryContext 	  contextOld;
+
+		/* This is an internal statement - do not log it */
+		internalStatement = true;
+
+		/* Make sure the fuction was fired as a trigger */
+		if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+			elog(ERROR, "not fired by event trigger manager");
+
+		/* Switch memory context */
+		contextQuery = AllocSetContextCreate(
+						CurrentMemoryContext,
+						"pgaudit_func_ddl_command_end temporary context",
+						ALLOCSET_DEFAULT_MINSIZE,
+						ALLOCSET_DEFAULT_INITSIZE,
+						ALLOCSET_DEFAULT_MAXSIZE);
+		contextOld = MemoryContextSwitchTo(contextQuery);
+
+		/* Get information about triggered events */
+		eventData = (EventTriggerData *) fcinfo->context;
+
+		/* Return objects affected by the (non drop) DDL statement */
+		query = "SELECT classid, objid, objsubid, UPPER(object_type), schema,\n"
+				"       identity, command\n"
+				"  FROM pg_event_trigger_get_creation_commands()";
+
+		/* Attempt to connect */
+		result = SPI_connect();
+
+		if (result < 0)
+			elog(ERROR, "pg_audit_ddl_command_end: SPI_connect returned %d",
+						result);
+
+		/* Execute the query */
+		result = SPI_execute(query, true, 0);
+
+		if (result != SPI_OK_SELECT)
+			elog(ERROR, "pg_audit_ddl_command_end: SPI_execute returned %d",
+						result);
+
+		/* Iterate returned rows */
+		spiTupDesc = SPI_tuptable->tupdesc;
+
+		for (row = 0; row < SPI_processed; row++)
+		{
+			HeapTuple  spiTuple;
+			bool	   isNull;
+
+			spiTuple = SPI_tuptable->vals[row];
+
+			/* Supply addition data to current audit event */
+			auditEventStack->auditEvent.logStmtLevel =
+				GetCommandLogLevel(eventData->parsetree);
+			auditEventStack->auditEvent.commandTag =
+				nodeTag(eventData->parsetree);
+			auditEventStack->auditEvent.command =
+				CreateCommandTag(eventData->parsetree);
+			auditEventStack->auditEvent.objectName =
+				SPI_getvalue(spiTuple, spiTupDesc, 6);
+			auditEventStack->auditEvent.objectType =
+				SPI_getvalue(spiTuple, spiTupDesc, 4);
+			auditEventStack->auditEvent.commandText =
+				TextDatumGetCString(
+					DirectFunctionCall1(pg_event_trigger_expand_command,
+										SPI_getbinval(spiTuple, spiTupDesc,
+													  7, &isNull)));
+
+			/* Log the audit event */
+			log_audit_event(auditEventStack);
+		}
+
+		/* Complete the query */
+		SPI_finish();
+
+		/* Switch to the old memory context */
+		MemoryContextSwitchTo(contextOld);
+		MemoryContextDelete(contextQuery);
+
+		/* No longer in an internal statement */
+		internalStatement = false;
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Supply additional data for drop statements that have event trigger support.
+ */
+Datum
+pg_audit_sql_drop(PG_FUNCTION_ARGS)
+{
+	if (auditLogBitmap & LOG_DDL)
+	{
+		EventTriggerData *eventData;
+		int				  result, row;
+		TupleDesc		  spiTupDesc;
+		const char		 *query;
+		MemoryContext 	  contextQuery;
+		MemoryContext 	  contextOld;
+
+		/* This is an internal statement - do not log it */
+		internalStatement = true;
+
+		/* Make sure the fuction was fired as a trigger */
+		if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+			elog(ERROR, "not fired by event trigger manager");
+
+		/* Switch memory context */
+		contextQuery = AllocSetContextCreate(
+						CurrentMemoryContext,
+						"pgaudit_func_ddl_command_end temporary context",
+						ALLOCSET_DEFAULT_MINSIZE,
+						ALLOCSET_DEFAULT_INITSIZE,
+						ALLOCSET_DEFAULT_MAXSIZE);
+		contextOld = MemoryContextSwitchTo(contextQuery);
+
+		/* Get information about triggered events */
+		eventData = (EventTriggerData *) fcinfo->context;
+
+		/* Return objects affected by the drop statement */
+		query = "SELECT classid, objid, objsubid, UPPER(object_type),\n"
+				"       schema_name, object_name, object_identity\n"
+				"  FROM pg_event_trigger_dropped_objects()";
+
+		/* Attempt to connect */
+		result = SPI_connect();
+
+		if (result < 0)
+			elog(ERROR, "pg_audit_ddl_drop: SPI_connect returned %d",
+						result);
+
+		/* Execute the query */
+		result = SPI_execute(query, true, 0);
+
+		if (result != SPI_OK_SELECT)
+			elog(ERROR, "pg_audit_ddl_drop: SPI_execute returned %d",
+						result);
+
+		/* Iterate returned rows */
+		spiTupDesc = SPI_tuptable->tupdesc;
+
+		for (row = 0; row < SPI_processed; row++)
+		{
+			HeapTuple  spiTuple;
+			char *schemaName;
+
+			spiTuple = SPI_tuptable->vals[row];
+
+			auditEventStack->auditEvent.objectType =
+				SPI_getvalue(spiTuple, spiTupDesc, 4);
+			schemaName = SPI_getvalue(spiTuple, spiTupDesc, 5);
+
+			if (!(pg_strcasecmp(auditEventStack->auditEvent.objectType,
+							"TYPE") == 0 ||
+				  pg_strcasecmp(schemaName, "pg_toast") == 0))
+			{
+				auditEventStack->auditEvent.objectName =
+						SPI_getvalue(spiTuple, spiTupDesc, 7);
+
+				log_audit_event(auditEventStack);
+			}
+		}
+
+		/* Complete the query */
+		SPI_finish();
+
+		/* Switch to the old memory context */
+		MemoryContextSwitchTo(contextOld);
+		MemoryContextDelete(contextQuery);
+
+		/* No longer in an internal statement */
+		internalStatement = false;
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pg_audit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_PARAMETER) == 0)
+			class = LOG_PARAMETER;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+			(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+			errmsg("pg_audit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pg_audit.role = "audit"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pg_audit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pg_audit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pg_audit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorStart_hook = ExecutorStart_hook;
+	ExecutorStart_hook = pgaudit_ExecutorStart_hook;
+
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ExecutorEnd_hook = ExecutorEnd_hook;
+	ExecutorEnd_hook = pgaudit_ExecutorEnd_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..6730c68
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pg_audit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..f5cbde2
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1412 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pg_audit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_PARAMETER => 'PARAMETER',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE					=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE			=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION			=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION		=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE			=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE				=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET			=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE				=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_COLUMN		=> 'ALTER TABLE COLUMN',
+	COMMAND_ALTER_TABLE_INDEX		=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN					=> 'BEGIN',
+	COMMAND_CLOSE					=> 'CLOSE CURSOR',
+	COMMAND_COMMIT					=> 'COMMIT',
+	COMMAND_COPY					=> 'COPY',
+	COMMAND_COPY_TO					=> 'COPY TO',
+	COMMAND_COPY_FROM				=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE		=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION		=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION		=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE			=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX			=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE				=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR			=> 'DECLARE CURSOR',
+	COMMAND_DO						=> 'DO',
+	COMMAND_DISCARD_ALL				=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION			=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE				=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA			=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE			=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS			=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE			=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA				=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE				=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_CONSTRAINT	=> 'DROP TABLE CONSTRAINT',
+	COMMAND_DROP_TABLE_INDEX		=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TOAST		=> 'DROP TABLE TOAST',
+	COMMAND_DROP_TABLE_TYPE			=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE					=> 'EXECUTE',
+	COMMAND_EXECUTE_READ			=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE			=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION		=> 'EXECUTE FUNCTION',
+	COMMAND_EXPLAIN					=> 'EXPLAIN',
+	COMMAND_FETCH					=> 'FETCH',
+	COMMAND_GRANT					=> 'GRANT',
+	COMMAND_INSERT					=> 'INSERT',
+	# COMMAND_PARAMETER				=> 'PARAMETER',
+	# COMMAND_PARAMETER_READ			=> 'PARAMETER_READ',
+	# COMMAND_PARAMETER_WRITE			=> 'PARAMETER_WRITE',
+	COMMAND_PREPARE					=> 'PREPARE',
+	COMMAND_PREPARE_READ			=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE			=> 'PREPARE WRITE',
+	COMMAND_REVOKE					=> 'REVOKE',
+	COMMAND_SELECT					=> 'SELECT',
+	COMMAND_SET						=> 'SET',
+	COMMAND_UPDATE					=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE					=> 'TYPE',
+	TYPE_NONE				=> '',
+
+	TYPE_AGGREGATE			=> 'AGGREGATE',
+	TYPE_COLLATION			=> 'COLLATION',
+	TYPE_CONVERSION			=> 'CONVERSION',
+	TYPE_SCHEMA				=> 'SCHEMA',
+	TYPE_FUNCTION			=> 'FUNCTION',
+	TYPE_INDEX				=> 'INDEX',
+	TYPE_TABLE				=> 'TABLE',
+	TYPE_TABLE_COLUMN		=> 'TABLE COLUMN',
+	TYPE_TABLE_CONSTRAINT	=> 'TABLE CONSTRAINT',
+	TYPE_TABLE_TOAST		=> 'TABLE TOAST',
+	TYPE_TYPE				=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pg_audit.log GUCS
+my %oAuditGrantHash;			# Hash to store pg_audit grants
+
+my $strCurrentAuditLog;		# pg_audit.log setting was Postgres was started with
+my $strTemporaryAuditLog;	# pg_audit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_AGGREGATE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_COLLATION},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_CONVERSION},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_COLUMN => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_COLUMN, &COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_AGGREGATE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_CONVERSION},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_COLLATION},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_FUNCTION},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_SCHEMA},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_CONSTRAINT => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_CONSTRAINT, &COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TOAST => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_TOAST, &COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXPLAIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+				   " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+				   " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+				   " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+	my $strParameter = shift;
+	my $bExpectError = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		eval
+		{
+			PgExecuteOnly($strSql);
+		};
+
+		if ($@ && !$bExpectError)
+		{
+			confess $@;
+		}
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $strParameter, $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# QuoteCSV
+################################################################################
+sub QuoteCSV
+{
+	my $strCSV = shift;
+
+	if (defined($strCSV) &&
+		(index($strCSV, ',') >= 0 || index($strCSV, '"') > 0 ||
+		 index($strCSV, "\n") > 0 || index($strCSV, "\r") >= 0))
+	{
+		$strCSV =~ s/"/""/g;
+		$strCSV = "\"${strCSV}\"";
+	}
+
+	return $strCSV;
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $strParameter = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Quote SQL if needs to be quoted
+	$strSql = QuoteCSV($strSql);
+
+	if (defined($strParameter))
+	{
+		$strSql .= ",${strParameter}";
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = QuoteCSV($oData);
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = QuoteCSV(${$oTableHash}{&NAME});
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+	$strLogActual =~ s/SESSION,[0-9]+,[0-9]+,/SESSION,/g;
+	$strLogActual =~ s/OBJECT,[0-9]+,[0-9]+,/OBJECT,/g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   " -c log_statement=all" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pg_audit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pg_audit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pg_audit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "GRANT " .
+								(defined($strColumn) ?
+									lc(${strPrivilege}) ." (${strColumn})" :
+									uc(${strPrivilege})) .
+								" ON TABLE ${strObject} TO ${strRole} ");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "REVOKE  " . uc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " ON TABLE ${strObject} FROM ${strRole} ");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable;	   # Store table info for select, insert, update, delete
+my $strSql;		# Hold Sql commands
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+
+$strSql = 'CREATE  TABLE  public.test (id pg_catalog.int4   )' .
+		  '  WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+$strSql = 'drop table test';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			&COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Create some email friendly tests.  These first tests are session logging only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+
+PgSetUser('user1');
+
+$strSql = 'CREATE  TABLE  public.account (id pg_catalog.int4   ,' .
+		  ' name pg_catalog.text   COLLATE pg_catalog."default", ' .
+		  'password pg_catalog.text   COLLATE pg_catalog."default", '.
+		  'description pg_catalog.text   COLLATE pg_catalog."default")  '.
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pg_audit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+				'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pg_audit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+
+$strSql = 'CREATE SCHEMA  test ';
+PgLogExecute(COMMAND_CREATE_SCHEMA, $strSql, 'test');
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+
+$strSql = 'CREATE  TABLE  test.pg_class  WITH (oids=OFF)   AS SELECT relname,' .
+		  ' relnamespace, reltype, reloftype, relowner, relam, relfilenode, ' .
+		  'reltablespace, relpages, reltuples, relallvisible, reltoastrelid, ' .
+		  'relhasindex, relisshared, relpersistence, relkind, relnatts, ' .
+		  'relchecks, relhasoids, relhaspkey, relhasrules, relhastriggers, ' .
+		  'relhassubclass, relrowsecurity, relispopulated, relreplident, ' .
+		  'relfrozenxid, relminmxid, relacl, reloptions ' .
+		  'FROM pg_catalog.pg_class ';
+PgLogExecute(COMMAND_INSERT, $strSql, undef, true, false);
+PgLogExecute(COMMAND_CREATE_TABLE_AS, $strSql, 'test.pg_class', false, true);
+
+$strSql = "COPY test.pg_class from '" . abs_path($strTestPath) . "/class.out'";
+PgLogExecute(COMMAND_INSERT, $strSql);
+#PgLogExecute(COMMAND_COPY_FROM, $strSql, undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+			 'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+$strSql = 'CREATE  TABLE  test.test_insert (id pg_catalog.int4   )  ' .
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'test.test_insert');
+
+$strSql = 'PREPARE pgclassstmt (oid) as insert into test.test_insert (id) ' .
+		  'values ($1)';
+PgLogExecute(COMMAND_PREPARE_WRITE, $strSql);
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, "1");
+
+$strSql = 'EXECUTE pgclassstmt (1)';
+PgLogExecute(COMMAND_EXECUTE_WRITE, $strSql, undef, true, true);
+
+# Create a table with a primary key
+$strSql = 'CREATE  TABLE  public.test (id pg_catalog.int4   , ' .
+		  'name pg_catalog.text   COLLATE pg_catalog."default", description ' .
+		  'pg_catalog.text   COLLATE pg_catalog."default", CONSTRAINT ' .
+		  'test_pkey PRIMARY KEY (id))  WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_INDEX, $strSql, 'public.test_pkey', true, false);
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test', false, true);
+
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+$strSql = 'GRANT SELECT ON TABLE public.test TO PUBLIC ';
+PgLogExecute(COMMAND_GRANT, $strSql);
+
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+
+$strSql = 'GRANT select (name) ON TABLE public.test TO PUBLIC ';
+PgLogExecute(COMMAND_GRANT, $strSql);
+
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Try a select that does not reference any tables
+PgLogExecute(COMMAND_SELECT, 'select 1, current_timestamp');
+
+# Now try the same in a do block
+$strSql = 'do $$ declare test int; begin select 1 into test; end $$';
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'select 1';
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, true);
+
+# Insert some data into test and try a loop in a do block
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (1)');
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (2)');
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (3)');
+
+$strSql = 'do $$ ' .
+		  'declare ' .
+		  '	result record;' .
+		  'begin ' .
+		  '	for result in select id from test loop ' .
+		  '		insert into test (id) values (result.id + 100); ' .
+		  '	end loop; ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'select id from test';
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false);
+
+$strSql = 'insert into test (id) values (result.id + 100)';
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+# Test EXECUTE with bind
+$strSql = "select * from test where id = ?";
+my $hStatement = $hDb->prepare($strSql);
+
+$strSql = "select * from test where id = \$1";
+$hStatement->bind_param(1, 101);
+$hStatement->execute();
+
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false, undef, "101");
+
+$hStatement->bind_param(1, 103);
+$hStatement->execute();
+
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false, undef, "103");
+
+$hStatement->finish();
+
+# Now try some DDL in a do block
+$strSql = 'do $$ ' .
+		  'begin ' .
+		  '	create table test_block (id int); ' .
+		  '	drop table test_block; ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'CREATE  TABLE  public.test_block (id pg_catalog.int4   )  ' .
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test_block', false, false);
+
+$strSql = 'drop table test_block';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'public.test_block', false, false);
+
+# Generate an error in a do block and make sure the stack gets cleaned up
+$strSql = 'do $$ ' .
+		  'begin ' .
+		  '	create table bobus.test_block (id int); ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, undef, undef, undef, undef, true);
+# PgLogExecute(COMMAND_SELECT, 'select 1');
+# exit 0;
+
+# Try explain
+PgLogExecute(COMMAND_SELECT, 'explain select 1', undef, true, false);
+PgLogExecute(COMMAND_EXPLAIN, 'explain select 1', undef, false, true);
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+# Test alter and drop table statements
+$strSql = 'ALTER TABLE public.test DROP COLUMN description ';
+PgLogExecute(COMMAND_ALTER_TABLE_COLUMN,
+			 $strSql, 'public.test.description', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 $strSql, 'public.test', false, true);
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+$strSql = 'ALTER TABLE  public.test RENAME TO test2';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'public.test2');
+
+$strSql = 'ALTER TABLE public.test2 SET SCHEMA test';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'test.test2');
+
+$strSql = 'ALTER TABLE test.test2 ADD COLUMN description pg_catalog.text   ' .
+		  'COLLATE pg_catalog."default"';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'test.test2');
+
+$strSql = 'ALTER TABLE test.test2 DROP COLUMN description ';
+PgLogExecute(COMMAND_ALTER_TABLE_COLUMN, $strSql,
+			 'test.test2.description', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql,
+			 'test.test2', false, true);
+
+$strSql = 'drop table test.test2';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'test.test2', true, false);
+PgLogExecute(COMMAND_DROP_TABLE_CONSTRAINT, $strSql, 'test_pkey on test.test2',
+			 false, false);
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, $strSql, 'test.test_pkey', false, true);
+
+$strSql = "CREATE  FUNCTION public.int_add(IN a pg_catalog.int4 , IN b " .
+		  "pg_catalog.int4 ) RETURNS  pg_catalog.int4 LANGUAGE plpgsql  " .
+		  "VOLATILE  CALLED ON NULL INPUT SECURITY INVOKER COST 100   AS '" .
+		  " begin return a + b; end '";
+PgLogExecute(COMMAND_CREATE_FUNCTION, $strSql,
+			 'public.int_add(integer,integer)');
+PgLogExecute(COMMAND_SELECT, "select int_add(1, 1)",
+							 undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add', false, true);
+
+$strSql = "CREATE AGGREGATE public.sum_test(  pg_catalog.int4) " .
+		  "(SFUNC=public.int_add, STYPE=pg_catalog.int4, INITCOND='0')";
+PgLogExecute(COMMAND_CREATE_AGGREGATE, $strSql, 'public.sum_test(integer)');
+
+# There's a bug here in deparse:
+$strSql = "ALTER AGGREGATE public.sum_test(integer) RENAME TO sum_test2";
+PgLogExecute(COMMAND_ALTER_AGGREGATE, $strSql, 'public.sum_test2(integer)');
+
+$strSql = "CREATE COLLATION public.collation_test (LC_COLLATE = 'de_DE', " .
+		  "LC_CTYPE = 'de_DE')";
+PgLogExecute(COMMAND_CREATE_COLLATION, $strSql, 'public.collation_test');
+
+$strSql =  "ALTER COLLATION public.collation_test RENAME TO collation_test2";
+PgLogExecute(COMMAND_ALTER_COLLATION, $strSql, 'public.collation_test2');
+
+$strSql = "CREATE  CONVERSION public.conversion_test FOR 'SQL_ASCII' " .
+		  "TO 'MULE_INTERNAL' FROM pg_catalog.ascii_to_mic";
+PgLogExecute(COMMAND_CREATE_CONVERSION, $strSql, 'public.conversion_test');
+
+$strSql = "ALTER CONVERSION public.conversion_test RENAME TO conversion_test2";
+PgLogExecute(COMMAND_ALTER_CONVERSION, $strSql, 'public.conversion_test2');
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 89fff77..6b0b407 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -125,6 +125,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..f9152cd
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,335 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pgaudit" xreflabel="pgaudit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pgaudit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pg_audit.log</literal>
+        GUC. There are six classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>PARAMETER</literal> - Parameters that were passed for the statement.  Parameters immediately follow the statement text. 
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pg_audit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pg_audit.log = 'all, -misc'
+          </programlisting>
+      </para>
+      
+      <para>
+      Note that <literal>pg_audit.log</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pg_audit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pg_audit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+
+      <para>
+      Note that <literal>pg_audit.role</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Authors</title>
+
+    <para>
+      Abhijit Menon-Sen <email>[email protected]</email>, Ian Barwick <email>[email protected]</email>, and David Steele <email>[email protected]</email>.
+    </para>
+  </sect2>
+</sect1>


  [application/pgp-signature] signature.asc (819B, ../../[email protected]/3-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-23 17:39  Sawada Masahiko <[email protected]>
  parent: David Steele <[email protected]>
  0 siblings, 2 replies; 279+ messages in thread

From: Sawada Masahiko @ 2015-03-23 17:39 UTC (permalink / raw)
  To: David Steele <[email protected]>; +Cc: Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Álvaro Herrera <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On Tue, Mar 24, 2015 at 1:40 AM, David Steele <[email protected]> wrote:
> Thanks for the review, Abhijit.
>
> On 3/23/15 1:31 AM, Abhijit Menon-Sen wrote:
>> At 2015-02-24 11:22:41 -0500, [email protected] wrote:
>>>
>>> Patch v3 is attached.
>>> +
>>> +    /* Function execution */
>>> +    LOG_MISC = (1 << 5),
>>
>> The comment above LOG_MISC should be changed.
>
> Fixed.
>
>> More fundamentally, this classification makes it easy to reuse LOGSTMT_*
>> (and a nice job you've done of that, with just a few additional special
>> cases), I don't think this level is quite enough for our needs. I think
>> it should at least be possible to specifically log commands that affect
>> privileges and roles.
>
> I agree, but this turns out to be easier said than done.  In the prior
> code for instance, CREATE ROLE was classified as USER, while ALTER ROLE
> .. RENAME was classified as DDL.  This is because any rename gets the
> command tag T_RenameStmt.  CreateCommandTag does return "ALTER ROLE",
> but now we're in the realm of string-matching again which is not my
> favorite thing.  Let me see if there is a clean way to get this
> accomplished.  I've also felt this is the one thing I'd like to see
> broken out.
>
>> I'm fond of finer categorisation for DDL as well, but I could live with
>> all DDL being lumped together.
>>
>> I'm experimenting with a few approaches to do this without reintroducing
>> switch statements to test every command. That will require core changes,
>> but I think we can find an acceptable arrangement. I'll post a proof of
>> concept in a few days.
>
> I also think finer-grained categorization would be best accomplished
> with some core changes.  It seemed too late to get those in for 9.5 so I
> decided to proceed with what I knew could be done reliably with the idea
> to improve it with core changes going forward.
>
> I look forward to your proof-of-concept.
>
>>> + * Takes an AuditEvent and, if it log_check(), writes it to the audit
>>> log.
>>
>> I don't think log_check is the most useful name, because this sentence
>> doesn't tell me what the function may do. Similarly, I would prefer to
>> have log_acl_check be renamed acl_grants_audit or similar. (These are
>> all static functions anyway, I don't think a log_ prefix is needed.)
>
> log_check() has become somewhat vestigial at this point since it is only
> called from one place - I've been considering removing it and merging
> into log_audit_event().  For the moment I've improved the comments.
>
> I like acl_grants_audit() and agree that it's a clearer name.  I'll
> incorporate that into the next version and apply the same scheme to the
> other ACL functionsas well as do a general review of naming.
>
>>> +    /* Free the column set */
>>> +    bms_free(tmpSet);
>>
>> (An aside, really: there are lots of comments like this, which I don't
>> think add anything to understanding the code, and should be removed.)
>
> I generally feel like you can't have too many comments.  I think even
> the less interesting/helpful comments help break the code into
> functional sections for readability.
>
>>> +            /*
>>> +             * We don't have access to the parsetree here, so we have to generate
>>> +             * the node type, object type, and command tag by decoding
>>> +             * rte->requiredPerms and rte->relkind.
>>> +             */
>>> +            auditEvent.logStmtLevel = LOGSTMT_MOD;
>>
>> (I am also trying to find a way to avoid having to do this.)
>
> That would be excellent.
>
>>> +            /* Set object type based on relkind */
>>> +            switch (class->relkind)
>>> +            {
>>> +                    case RELKIND_RELATION:
>>> +                            utilityAuditEvent.objectType = OBJECT_TYPE_TABLE;
>>> +                            break;
>>
>> This occurs elsewhere too. But I suppose new relkinds are added less
>> frequently than new commands.
>
> Well, that's the hope at least.  I should mention that ALL statements
> will be logged no matter what additional classification happens.  The
> amount of information returned may not be ideal, but nothing is ever
> excluded from logging (depending on the classes selected, of course).
>
>> Again on a larger level, I'm not sure how I feel about _avoiding_ the
>> use of event triggers for audit logging. Regardless of whether we use
>> the deparse code (which I personally think is a good idea; Álvaro has
>> been working on it, and it looks very nice) to log extra information,
>> using the object access hook inevitably means we have to reimplement
>> the identification/classification code here.
>>
>> In "old" pgaudit, I think that extra effort is justified by the need to
>> be backwards compatible with pre-event trigger releases. In a 9.5-only
>> version, I am not at all convinced that this makes sense.
>>
>> Thoughts?
>
> I was nervous about basing pg_audit on code that I wasn't sure would be
> committed (at the time).  Since pg_event_trigger_get_creation_commands()
> is tied up with deparse, I honestly didn't feel like the triggers were
> bringing much to the table.
>
> That being said, I agree that the deparse code is very useful and now
> looks certain to be committed for 9.5.
>
> I have prepared a patch that brings event triggers and deparse back to
> pg_audit based on the Alvaro's dev/deparse branch at
> git://git.postgresql.org/git/2ndquadrant_bdr.git (commit 0447fc5).  I've
> updated the unit tests accordingly.
>
> I left in the OAT code for now.  It does add detail to one event that
> the event triggers do not handle (creating PK indexes) and I feel that
> it lends an element of safety in case something happens to the event
> triggers.  OAT is also required for function calls so the code path
> cannot be eliminated entirely.  I'm not committed to keeping the
> redundant OAT code, but I'd rather not remove it until deparse is
> committed to master.
>
> I've been hesitant to post this patch as it will not work in master
> (though it will compile), but I don't want to hold on to it any longer
> since the end of the CF is nominally just weeks away.  If you want to
> run the patch in master, you'll need to disable the
> pg_audit_ddl_command_end trigger.
>
> I've also addressed Fujii's concerns about logging parameters - this is
> now an option.  The event stack has been formalized and
> MemoryContextRegisterResetCallback() is used to cleanup the stack on errors.
>
> Let me know what you think.
>

Hi,

I tied to look into latest patch, but got following error.

masahiko [pg_audit] $ LANG=C make
gcc -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels
-Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
-fwrapv -g -fpic -I. -I. -I../../src/include -D_GNU_SOURCE   -c -o
pg_audit.o pg_audit.c
pg_audit.c: In function 'log_audit_event':
pg_audit.c:456: warning: ISO C90 forbids mixed declarations and code
pg_audit.c: In function 'pg_audit_ddl_command_end':
pg_audit.c:1436: error: 'pg_event_trigger_expand_command' undeclared
(first use in this function)
pg_audit.c:1436: error: (Each undeclared identifier is reported only once
pg_audit.c:1436: error: for each function it appears in.)
make: *** [pg_audit.o] Error 1

Am I missing something?

Regards,

-------
Sawada Masahiko


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-23 18:17  Alvaro Herrera <[email protected]>
  parent: Sawada Masahiko <[email protected]>
  1 sibling, 1 reply; 279+ messages in thread

From: Alvaro Herrera @ 2015-03-23 18:17 UTC (permalink / raw)
  To: Sawada Masahiko <[email protected]>; +Cc: David Steele <[email protected]>; Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Sawada Masahiko wrote:

> I tied to look into latest patch, but got following error.
> 
> masahiko [pg_audit] $ LANG=C make
> gcc -Wall -Wmissing-prototypes -Wpointer-arith
> -Wdeclaration-after-statement -Wendif-labels
> -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
> -fwrapv -g -fpic -I. -I. -I../../src/include -D_GNU_SOURCE   -c -o
> pg_audit.o pg_audit.c
> pg_audit.c: In function 'log_audit_event':
> pg_audit.c:456: warning: ISO C90 forbids mixed declarations and code
> pg_audit.c: In function 'pg_audit_ddl_command_end':
> pg_audit.c:1436: error: 'pg_event_trigger_expand_command' undeclared
> (first use in this function)

You need to apply my deparsing patch first, last version of which I
posted here:
https://www.postgresql.org/message-id/[email protected]

-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-23 18:30  David Steele <[email protected]>
  parent: Sawada Masahiko <[email protected]>
  1 sibling, 0 replies; 279+ messages in thread

From: David Steele @ 2015-03-23 18:30 UTC (permalink / raw)
  To: Sawada Masahiko <[email protected]>; +Cc: Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Álvaro Herrera <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 3/23/15 1:39 PM, Sawada Masahiko wrote:
> On Tue, Mar 24, 2015 at 1:40 AM, David Steele <[email protected]> wrote:
>>
>> I have prepared a patch that brings event triggers and deparse back to
>> pg_audit based on the Alvaro's dev/deparse branch at
>> git://git.postgresql.org/git/2ndquadrant_bdr.git (commit 0447fc5).  I've
>> updated the unit tests accordingly.
>>
>> I've been hesitant to post this patch as it will not work in master
>> (though it will compile), but I don't want to hold on to it any longer
>> since the end of the CF is nominally just weeks away.  If you want to
>> run the patch in master, you'll need to disable the
>> pg_audit_ddl_command_end trigger.
> 
> Hi,
> 
> I tied to look into latest patch, but got following error.
> 
> masahiko [pg_audit] $ LANG=C make
> gcc -Wall -Wmissing-prototypes -Wpointer-arith
> -Wdeclaration-after-statement -Wendif-labels
> -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
> -fwrapv -g -fpic -I. -I. -I../../src/include -D_GNU_SOURCE   -c -o
> pg_audit.o pg_audit.c
> pg_audit.c: In function 'log_audit_event':
> pg_audit.c:456: warning: ISO C90 forbids mixed declarations and code
> pg_audit.c: In function 'pg_audit_ddl_command_end':
> pg_audit.c:1436: error: 'pg_event_trigger_expand_command' undeclared
> (first use in this function)
> pg_audit.c:1436: error: (Each undeclared identifier is reported only once
> pg_audit.c:1436: error: for each function it appears in.)
> make: *** [pg_audit.o] Error 1
> 
> Am I missing something?
> 

It's my mistake.  I indicated that this would compile under master - but
that turns out not to be true because of this function.  It will only
compile cleanly in Alvaro's branch mentioned above.

My apologies - this is why I have been hesitant to post this patch
before.  You are welcome to try with Alvaro's deparse branch or wait
until it has been committed to master.

I've attached patch v5 only to cleanup the warnings you saw.

-- 
- David Steele
[email protected]

diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..9d9ee83
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,22 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
+
+CREATE FUNCTION pg_audit_ddl_command_end()
+	RETURNS event_trigger
+	LANGUAGE C
+	AS 'MODULE_PATHNAME', 'pg_audit_ddl_command_end';
+
+CREATE EVENT TRIGGER pg_audit_ddl_command_end
+	ON ddl_command_end
+	EXECUTE PROCEDURE pg_audit_ddl_command_end();
+
+CREATE FUNCTION pg_audit_sql_drop()
+	RETURNS event_trigger
+	LANGUAGE C
+	AS 'MODULE_PATHNAME', 'pg_audit_sql_drop';
+
+CREATE EVENT TRIGGER pg_audit_sql_drop
+	ON sql_drop
+	EXECUTE PROCEDURE pg_audit_sql_drop();
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..65c8ed2
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1712 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements (See
+ * pg_audit.sgml for details).
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_audit/pg_audit.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * Event trigger prototypes
+ */
+Datum pg_audit_ddl_command_end(PG_FUNCTION_ARGS);
+Datum pg_audit_sql_drop(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(pg_audit_ddl_command_end);
+PG_FUNCTION_INFO_V1(pg_audit_sql_drop);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String constants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC			"MISC"
+#define CLASS_PARAMETER		"PARAMETER"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 1),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 2),
+
+	/* Statements not covered by another class */
+	LOG_MISC = (1 << 3),
+
+	/* Function execution */
+	LOG_PARAMETER = (1 << 4),
+
+	/* SELECT */
+	LOG_READ = (1 << 5),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 6),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String constants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If a statement affects multiple objects multiple AuditEvents must be
+ * created to represent it.
+ */
+typedef struct
+{
+	int64 statementId;
+	int64 substatementId;
+
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	ParamListInfo paramList;
+
+	bool granted;
+	bool logged;
+} AuditEvent;
+
+/*
+ * A simple FIFO queue to keep track of the current stack of audit events.
+ */
+typedef struct AuditEventStackItem
+{
+	struct AuditEventStackItem *next;
+
+	AuditEvent auditEvent;
+
+	MemoryContext contextAudit;
+	MemoryContextCallback contextCallback;
+} AuditEventStackItem;
+
+AuditEventStackItem *auditEventStack = NULL;
+
+/*
+ * Track when an internal statement is running so it is not logged
+ */
+static bool internalStatement = false;
+
+/*
+ * Track running total for statements and substatements and whether or not
+ * anything has been logged since this statement began.
+ */
+static uint64 statementTotal = 0;
+static uint64 substatementTotal = 0;
+
+static bool statementLogged = false;
+
+/*
+ * Stack functions
+ *
+ * Audit events can go down to multiple levels so a stack is maintained to keep
+ * track of them.
+ */
+
+/*
+ * Respond to callbacks registered with MemoryContextRegisterResetCallback().
+ * Removes the event(s) off the stack that have become obsolete once the
+ * MemoryContext has been freed.  The callback should always be freeing the top
+ * of the stack, but the code is tolerant of out-of-order callbacks.
+ */
+static void
+stack_free(void *stackFree)
+{
+	AuditEventStackItem *nextItem = auditEventStack;
+
+	/* Only process if the stack contains items */
+	while (nextItem != NULL)
+	{
+		/* Check if this item matches the item to be freed */
+		if (nextItem == (AuditEventStackItem *)stackFree)
+		{
+			/* Move top of stack the the item after the freed item */
+			auditEventStack = nextItem->next;
+
+			/* If the stack is not empty */
+			if (auditEventStack == NULL)
+			{
+				/* Reset internal statement in case of error */
+				internalStatement = false;
+
+				/* Reset sub statement total */
+				substatementTotal = 0;
+
+				/* Reset statement logged flag total */
+				statementLogged = false;
+			}
+
+			return;
+		}
+
+		/* Still looking, test the next item */
+		nextItem = nextItem->next;
+	}
+}
+
+/*
+ * Push a new audit event onto the stack and create a new memory context to
+ * store it.
+ */
+static AuditEventStackItem *
+stack_push()
+{
+	MemoryContext contextAudit;
+	MemoryContext contextOld;
+	AuditEventStackItem *stackItem;
+
+	/* Create a new memory context */
+	contextAudit = AllocSetContextCreate(CurrentMemoryContext,
+										 "pg_audit stack context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+	contextOld = MemoryContextSwitchTo(contextAudit);
+
+	/* Allocate the stack item */
+	stackItem = palloc0(sizeof(AuditEventStackItem));
+
+	/* Store memory contexts */
+	stackItem->contextAudit = contextAudit;
+
+	/* If item already on stack then push it down */
+	if (auditEventStack != NULL)
+		stackItem->next = auditEventStack;
+	else
+		stackItem->next = NULL;
+
+	/*
+	 * Setup a callback in case an error happens.  stack_free() will truncate
+	 * the stack at this item.
+	 */
+	stackItem->contextCallback.func = stack_free;
+	stackItem->contextCallback.arg = (void *)stackItem;
+	MemoryContextRegisterResetCallback(contextAudit,
+									   &stackItem->contextCallback);
+
+	/* Push item on the stack */
+	auditEventStack = stackItem;
+
+	/* Return to the old memory context */
+	MemoryContextSwitchTo(contextOld);
+
+	/* Return the stack item */
+	return stackItem;
+}
+
+/*
+ * Pop an audit event from the stack by deleting the memory context that
+ * contains it.  The callback to stack_free() does the actual pop.
+ */
+static void
+stack_pop()
+{
+	/* Error if the stack is already empty */
+	if (auditEventStack == NULL)
+		elog(ERROR, "pg_audit stack is already empty");
+
+	/* Switch the old memory context and delete the audit context */
+	MemoryContextDelete(auditEventStack->contextAudit);
+}
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Appends a properly quoted CSV field to StringInfo.
+ */
+static void
+append_valid_csv(StringInfoData *buffer, const char *appendStr)
+{
+	const char *pChar;
+
+	/*
+	 * If the append string is null then return.  NULL fields are not quoted
+	 * in CSV
+	 */
+	if (appendStr == NULL)
+		return;
+
+	/* Only format for CSV if appendStr contains: ", comma, \n, \r */
+	if (strstr(appendStr, ",") || strstr(appendStr, "\"") ||
+		strstr(appendStr, "\n") || strstr(appendStr, "\r"))
+	{
+		appendStringInfoCharMacro(buffer, '"');
+
+		for (pChar = appendStr; *pChar; pChar++)
+		{
+			if (*pChar == '"') /* double single quotes */
+				appendStringInfoCharMacro(buffer, *pChar);
+
+			appendStringInfoCharMacro(buffer, *pChar);
+		}
+
+		appendStringInfoCharMacro(buffer, '"');
+	}
+	/* Else just append */
+	else
+	{
+		appendStringInfoString(buffer, appendStr);
+	}
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEventStackItem *stackItem)
+{
+	const char *classname;
+	MemoryContext contextOld;
+	StringInfoData auditStr;
+
+	/* Check that this event should be logged. */
+	if (!log_check(&stackItem->auditEvent, &classname))
+		return;
+
+	/* Use audit memory context in case something is not freed */
+	contextOld = MemoryContextSwitchTo(stackItem->contextAudit);
+
+	/* Set statement and substatement Ids */
+	if (stackItem->auditEvent.statementId == 0)
+	{
+		/* If nothing has been logged yet then create a new statement Id */
+		if (!statementLogged)
+		{
+			statementTotal++;
+			statementLogged = true;
+		}
+
+		stackItem->auditEvent.statementId = statementTotal;
+		stackItem->auditEvent.substatementId = ++substatementTotal;
+	}
+
+	/* Create the audit string */
+	initStringInfo(&auditStr);
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.command);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.objectType);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.objectName);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.commandText);
+
+	/* If parameter logging is turned on and there are parameters to log */
+	if (auditLogBitmap & LOG_PARAMETER &&
+		stackItem->auditEvent.paramList != NULL &&
+		stackItem->auditEvent.paramList->numParams > 0 &&
+		!IsAbortedTransactionBlockState())
+	{
+		ParamListInfo paramList = stackItem->auditEvent.paramList;
+		int paramIdx;
+
+		/* Iterate through all params */
+		for (paramIdx = 0; paramIdx < paramList->numParams; paramIdx++)
+		{
+			ParamExternData *prm = &paramList->params[paramIdx];
+			Oid 			 typeOutput;
+			bool 			 typeIsVarLena;
+			char 			*paramStr;
+
+			/* Add a comma for each param */
+			appendStringInfoCharMacro(&auditStr, ',');
+
+			/* Skip this param if null or if oid is invalid */
+			if (prm->isnull || !OidIsValid(prm->ptype))
+			{
+				continue;
+			}
+
+			/* Output the string */
+			getTypeOutputInfo(prm->ptype, &typeOutput, &typeIsVarLena);
+			paramStr = OidOutputFunctionCall(typeOutput, prm->value);
+
+			append_valid_csv(&auditStr, paramStr);
+			pfree(paramStr);
+		}
+	}
+
+	/* Log the audit string */
+	ereport(LOG,
+		(errmsg("AUDIT: %s,%ld,%ld,%s,%s",
+			stackItem->auditEvent.granted ?
+				AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+			stackItem->auditEvent.statementId,
+			stackItem->auditEvent.substatementId,
+			classname, auditStr.data),
+		 errhidestmt(true)));
+
+	/* Mark the audit event as logged */
+	stackItem->auditEvent.logged = true;
+
+	/* Switch back to the old memory context */
+	MemoryContextSwitchTo(contextOld);
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem	   *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* bms_first_member is destructive, so make a copy before using it. */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for SELECT/DML operations via executor permissions checks.
+ */
+static void
+log_select_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+	bool found = false;
+
+	/* Do not log if this is an internal statement */
+	if (internalStatement)
+		return;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		found = true;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			continue;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_InsertStmt;
+			auditEventStack->auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_UpdateStmt;
+			auditEventStack->auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_DeleteStmt;
+			auditEventStack->auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEventStack->auditEvent.commandTag = T_SelectStmt;
+			auditEventStack->auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEventStack->auditEvent.commandTag = T_Invalid;
+			auditEventStack->auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEventStack->auditEvent.granted = false;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEventStack->auditEvent.objectName = "";
+			auditEventStack->auditEvent.objectType = "";
+
+			log_audit_event(auditEventStack);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEventStack->auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEventStack->auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEventStack->auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEventStack->auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEventStack->auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEventStack->auditEvent.granted)
+		{
+			auditEventStack->auditEvent.logged = false;
+			log_audit_event(auditEventStack);
+		}
+
+		pfree(auditEventStack->auditEvent.objectName);
+	}
+
+	/*
+	 * If no tables were found that means that RangeTbls was empty or all
+	 * relations were in the system schema.  In that case still log a
+	 * session record.
+	 */
+	if (!found)
+	{
+		auditEventStack->auditEvent.granted = false;
+		auditEventStack->auditEvent.logged = false;
+
+		log_audit_event(auditEventStack);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		auditEventStack->auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+	AuditEventStackItem *stackItem;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Push audit event onto the stack */
+	stackItem = stack_push();
+
+	/* Generate the fully-qualified function name. */
+	stackItem->auditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the function call */
+	stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+	stackItem->auditEvent.commandTag = T_DoStmt;
+	stackItem->auditEvent.command = COMMAND_EXECUTE;
+	stackItem->auditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	stackItem->auditEvent.commandText = stackItem->next->auditEvent.commandText;
+
+	log_audit_event(stackItem);
+
+	/* Pop audit event from the stack */
+	stack_pop();
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute */
+		case OAT_FUNCTION_EXECUTE:
+			if (auditLogBitmap & LOG_FUNCTION)
+				log_function_execute(objectId);
+			break;
+
+		/* Log create */
+		case OAT_POST_CREATE:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter */
+		case OAT_POST_ALTER:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop */
+		case OAT_DROP:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessDrop *drop = arg;
+
+				if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
+static ExecutorEnd_hook_type next_ExecutorEnd_hook = NULL;
+
+/*
+ * Hook ExecutorStart to get the query text and basic command type for queries
+ * that do not contain a table so can't be idenitified accurately in
+ * ExecutorCheckPerms.
+ */
+static void
+pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	if (!internalStatement)
+	{
+		/* Allocate the audit event */
+		stackItem = stack_push();
+
+		/* Initialize command */
+		switch (queryDesc->operation)
+		{
+			case CMD_SELECT:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+				stackItem->auditEvent.commandTag = T_SelectStmt;
+				stackItem->auditEvent.command = COMMAND_SELECT;
+				break;
+
+			case CMD_INSERT:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_InsertStmt;
+				stackItem->auditEvent.command = COMMAND_INSERT;
+				break;
+
+			case CMD_UPDATE:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_UpdateStmt;
+				stackItem->auditEvent.command = COMMAND_UPDATE;
+				break;
+
+			case CMD_DELETE:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_DeleteStmt;
+				stackItem->auditEvent.command = COMMAND_DELETE;
+				break;
+
+			default:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+				stackItem->auditEvent.commandTag = T_Invalid;
+				stackItem->auditEvent.command = COMMAND_UNKNOWN;
+				break;
+		}
+
+		/* Initialize the audit event */
+		stackItem->auditEvent.objectName = "";
+		stackItem->auditEvent.objectType = "";
+		stackItem->auditEvent.commandText = queryDesc->sourceText;
+		stackItem->auditEvent.paramList = queryDesc->params;
+	}
+
+	/* Call the previous hook or standard function */
+	if (next_ExecutorStart_hook)
+		next_ExecutorStart_hook(queryDesc, eflags);
+	else
+		standard_ExecutorStart(queryDesc, eflags);
+}
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid;
+
+	/* Get the audit oid if the role exists. */
+	auditOid = get_role_oid(auditRole, true);
+
+	/* Log DML if the audit role is valid or session logging is enabled. */
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_select_dml(auditOid, rangeTabls);
+
+	/* Call the next hook function. */
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ExecutorEnd to pop statement audit event off the stack.
+ */
+static void
+pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
+{
+	/* Call the next hook or standard function */
+	if (next_ExecutorEnd_hook)
+		next_ExecutorEnd_hook(queryDesc);
+	else
+		standard_ExecutorEnd(queryDesc);
+
+	/* Pop the audit event off the stack */
+	if (!internalStatement)
+	{
+		stack_pop();
+	}
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	/* Allocate the audit event */
+	if (!IsAbortedTransactionBlockState())
+	{
+		/* Process top level utility statement */
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			if (auditEventStack != NULL)
+				elog(ERROR, "pg_audit stack is not empty");
+
+			/* Set params */
+			stackItem = stack_push();
+			stackItem->auditEvent.paramList = params;
+		}
+		else
+			stackItem = stack_push();
+
+		stackItem->auditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+		stackItem->auditEvent.commandTag = nodeTag(parsetree);
+		stackItem->auditEvent.command = CreateCommandTag(parsetree);
+		stackItem->auditEvent.objectName = "";
+		stackItem->auditEvent.objectType = "";
+		stackItem->auditEvent.commandText = queryString;
+
+		/*
+		 * If this is a DO block log it before calling the next ProcessUtility
+		 * hook.
+		 */
+		if (auditLogBitmap != 0 &&
+			stackItem->auditEvent.commandTag == T_DoStmt &&
+			!IsAbortedTransactionBlockState())
+		{
+			log_audit_event(stackItem);
+		}
+	}
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Process the audit event if there is one. */
+	if (stackItem != NULL)
+	{
+		/* Log the utility command if logging is on, the command has not already
+		 * been logged by another hook, and the transaction is not aborted. */
+		if (auditLogBitmap != 0 && !stackItem->auditEvent.logged &&
+			!IsAbortedTransactionBlockState())
+			log_audit_event(stackItem);
+
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			while (auditEventStack != NULL)
+				stack_pop();
+		}
+		else
+			stack_pop();
+	}
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState() &&
+		auditLogBitmap & (LOG_DDL | LOG_FUNCTION))
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * Event trigger functions
+ */
+
+/*
+ * Supply additional data for (non drop) statements that have event trigger
+ * support and can be deparsed.
+ */
+Datum
+pg_audit_ddl_command_end(PG_FUNCTION_ARGS)
+{
+	/* Continue only if session logging is enabled */
+	if (auditLogBitmap != LOG_DDL)
+	{
+		EventTriggerData *eventData;
+		int				  result, row;
+		TupleDesc		  spiTupDesc;
+		const char		 *query;
+		MemoryContext 	  contextQuery;
+		MemoryContext 	  contextOld;
+
+		/* This is an internal statement - do not log it */
+		internalStatement = true;
+
+		/* Make sure the fuction was fired as a trigger */
+		if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+			elog(ERROR, "not fired by event trigger manager");
+
+		/* Switch memory context */
+		contextQuery = AllocSetContextCreate(
+						CurrentMemoryContext,
+						"pgaudit_func_ddl_command_end temporary context",
+						ALLOCSET_DEFAULT_MINSIZE,
+						ALLOCSET_DEFAULT_INITSIZE,
+						ALLOCSET_DEFAULT_MAXSIZE);
+		contextOld = MemoryContextSwitchTo(contextQuery);
+
+		/* Get information about triggered events */
+		eventData = (EventTriggerData *) fcinfo->context;
+
+		/* Return objects affected by the (non drop) DDL statement */
+		query = "SELECT classid, objid, objsubid, UPPER(object_type), schema,\n"
+				"       identity, command\n"
+				"  FROM pg_event_trigger_get_creation_commands()";
+
+		/* Attempt to connect */
+		result = SPI_connect();
+
+		if (result < 0)
+			elog(ERROR, "pg_audit_ddl_command_end: SPI_connect returned %d",
+						result);
+
+		/* Execute the query */
+		result = SPI_execute(query, true, 0);
+
+		if (result != SPI_OK_SELECT)
+			elog(ERROR, "pg_audit_ddl_command_end: SPI_execute returned %d",
+						result);
+
+		/* Iterate returned rows */
+		spiTupDesc = SPI_tuptable->tupdesc;
+
+		for (row = 0; row < SPI_processed; row++)
+		{
+			HeapTuple  spiTuple;
+			bool	   isNull;
+
+			spiTuple = SPI_tuptable->vals[row];
+
+			/* Supply addition data to current audit event */
+			auditEventStack->auditEvent.logStmtLevel =
+				GetCommandLogLevel(eventData->parsetree);
+			auditEventStack->auditEvent.commandTag =
+				nodeTag(eventData->parsetree);
+			auditEventStack->auditEvent.command =
+				CreateCommandTag(eventData->parsetree);
+			auditEventStack->auditEvent.objectName =
+				SPI_getvalue(spiTuple, spiTupDesc, 6);
+			auditEventStack->auditEvent.objectType =
+				SPI_getvalue(spiTuple, spiTupDesc, 4);
+			auditEventStack->auditEvent.commandText =
+				TextDatumGetCString(
+					DirectFunctionCall1(pg_event_trigger_expand_command,
+										SPI_getbinval(spiTuple, spiTupDesc,
+													  7, &isNull)));
+
+			/* Log the audit event */
+			log_audit_event(auditEventStack);
+		}
+
+		/* Complete the query */
+		SPI_finish();
+
+		/* Switch to the old memory context */
+		MemoryContextSwitchTo(contextOld);
+		MemoryContextDelete(contextQuery);
+
+		/* No longer in an internal statement */
+		internalStatement = false;
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Supply additional data for drop statements that have event trigger support.
+ */
+Datum
+pg_audit_sql_drop(PG_FUNCTION_ARGS)
+{
+	if (auditLogBitmap & LOG_DDL)
+	{
+		int				  result, row;
+		TupleDesc		  spiTupDesc;
+		const char		 *query;
+		MemoryContext 	  contextQuery;
+		MemoryContext 	  contextOld;
+
+		/* This is an internal statement - do not log it */
+		internalStatement = true;
+
+		/* Make sure the fuction was fired as a trigger */
+		if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+			elog(ERROR, "not fired by event trigger manager");
+
+		/* Switch memory context */
+		contextQuery = AllocSetContextCreate(
+						CurrentMemoryContext,
+						"pgaudit_func_ddl_command_end temporary context",
+						ALLOCSET_DEFAULT_MINSIZE,
+						ALLOCSET_DEFAULT_INITSIZE,
+						ALLOCSET_DEFAULT_MAXSIZE);
+		contextOld = MemoryContextSwitchTo(contextQuery);
+
+		/* Return objects affected by the drop statement */
+		query = "SELECT classid, objid, objsubid, UPPER(object_type),\n"
+				"       schema_name, object_name, object_identity\n"
+				"  FROM pg_event_trigger_dropped_objects()";
+
+		/* Attempt to connect */
+		result = SPI_connect();
+
+		if (result < 0)
+			elog(ERROR, "pg_audit_ddl_drop: SPI_connect returned %d",
+						result);
+
+		/* Execute the query */
+		result = SPI_execute(query, true, 0);
+
+		if (result != SPI_OK_SELECT)
+			elog(ERROR, "pg_audit_ddl_drop: SPI_execute returned %d",
+						result);
+
+		/* Iterate returned rows */
+		spiTupDesc = SPI_tuptable->tupdesc;
+
+		for (row = 0; row < SPI_processed; row++)
+		{
+			HeapTuple  spiTuple;
+			char *schemaName;
+
+			spiTuple = SPI_tuptable->vals[row];
+
+			auditEventStack->auditEvent.objectType =
+				SPI_getvalue(spiTuple, spiTupDesc, 4);
+			schemaName = SPI_getvalue(spiTuple, spiTupDesc, 5);
+
+			if (!(pg_strcasecmp(auditEventStack->auditEvent.objectType,
+							"TYPE") == 0 ||
+				  pg_strcasecmp(schemaName, "pg_toast") == 0))
+			{
+				auditEventStack->auditEvent.objectName =
+						SPI_getvalue(spiTuple, spiTupDesc, 7);
+
+				log_audit_event(auditEventStack);
+			}
+		}
+
+		/* Complete the query */
+		SPI_finish();
+
+		/* Switch to the old memory context */
+		MemoryContextSwitchTo(contextOld);
+		MemoryContextDelete(contextQuery);
+
+		/* No longer in an internal statement */
+		internalStatement = false;
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pg_audit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_PARAMETER) == 0)
+			class = LOG_PARAMETER;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+			(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+			errmsg("pg_audit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pg_audit.role = "audit"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pg_audit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pg_audit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pg_audit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorStart_hook = ExecutorStart_hook;
+	ExecutorStart_hook = pgaudit_ExecutorStart_hook;
+
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ExecutorEnd_hook = ExecutorEnd_hook;
+	ExecutorEnd_hook = pgaudit_ExecutorEnd_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..6730c68
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pg_audit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..f5cbde2
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1412 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pg_audit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_PARAMETER => 'PARAMETER',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE					=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE			=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION			=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION		=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE			=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE				=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET			=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE				=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_COLUMN		=> 'ALTER TABLE COLUMN',
+	COMMAND_ALTER_TABLE_INDEX		=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN					=> 'BEGIN',
+	COMMAND_CLOSE					=> 'CLOSE CURSOR',
+	COMMAND_COMMIT					=> 'COMMIT',
+	COMMAND_COPY					=> 'COPY',
+	COMMAND_COPY_TO					=> 'COPY TO',
+	COMMAND_COPY_FROM				=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE		=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION		=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION		=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE			=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX			=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE				=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR			=> 'DECLARE CURSOR',
+	COMMAND_DO						=> 'DO',
+	COMMAND_DISCARD_ALL				=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION			=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE				=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA			=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE			=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS			=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE			=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA				=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE				=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_CONSTRAINT	=> 'DROP TABLE CONSTRAINT',
+	COMMAND_DROP_TABLE_INDEX		=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TOAST		=> 'DROP TABLE TOAST',
+	COMMAND_DROP_TABLE_TYPE			=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE					=> 'EXECUTE',
+	COMMAND_EXECUTE_READ			=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE			=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION		=> 'EXECUTE FUNCTION',
+	COMMAND_EXPLAIN					=> 'EXPLAIN',
+	COMMAND_FETCH					=> 'FETCH',
+	COMMAND_GRANT					=> 'GRANT',
+	COMMAND_INSERT					=> 'INSERT',
+	# COMMAND_PARAMETER				=> 'PARAMETER',
+	# COMMAND_PARAMETER_READ			=> 'PARAMETER_READ',
+	# COMMAND_PARAMETER_WRITE			=> 'PARAMETER_WRITE',
+	COMMAND_PREPARE					=> 'PREPARE',
+	COMMAND_PREPARE_READ			=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE			=> 'PREPARE WRITE',
+	COMMAND_REVOKE					=> 'REVOKE',
+	COMMAND_SELECT					=> 'SELECT',
+	COMMAND_SET						=> 'SET',
+	COMMAND_UPDATE					=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE					=> 'TYPE',
+	TYPE_NONE				=> '',
+
+	TYPE_AGGREGATE			=> 'AGGREGATE',
+	TYPE_COLLATION			=> 'COLLATION',
+	TYPE_CONVERSION			=> 'CONVERSION',
+	TYPE_SCHEMA				=> 'SCHEMA',
+	TYPE_FUNCTION			=> 'FUNCTION',
+	TYPE_INDEX				=> 'INDEX',
+	TYPE_TABLE				=> 'TABLE',
+	TYPE_TABLE_COLUMN		=> 'TABLE COLUMN',
+	TYPE_TABLE_CONSTRAINT	=> 'TABLE CONSTRAINT',
+	TYPE_TABLE_TOAST		=> 'TABLE TOAST',
+	TYPE_TYPE				=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pg_audit.log GUCS
+my %oAuditGrantHash;			# Hash to store pg_audit grants
+
+my $strCurrentAuditLog;		# pg_audit.log setting was Postgres was started with
+my $strTemporaryAuditLog;	# pg_audit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_AGGREGATE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_COLLATION},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_CONVERSION},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_COLUMN => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_COLUMN, &COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_AGGREGATE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_CONVERSION},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_COLLATION},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_FUNCTION},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_SCHEMA},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_CONSTRAINT => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_CONSTRAINT, &COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TOAST => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_TOAST, &COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXPLAIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+				   " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+				   " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+				   " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+	my $strParameter = shift;
+	my $bExpectError = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		eval
+		{
+			PgExecuteOnly($strSql);
+		};
+
+		if ($@ && !$bExpectError)
+		{
+			confess $@;
+		}
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $strParameter, $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# QuoteCSV
+################################################################################
+sub QuoteCSV
+{
+	my $strCSV = shift;
+
+	if (defined($strCSV) &&
+		(index($strCSV, ',') >= 0 || index($strCSV, '"') > 0 ||
+		 index($strCSV, "\n") > 0 || index($strCSV, "\r") >= 0))
+	{
+		$strCSV =~ s/"/""/g;
+		$strCSV = "\"${strCSV}\"";
+	}
+
+	return $strCSV;
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $strParameter = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Quote SQL if needs to be quoted
+	$strSql = QuoteCSV($strSql);
+
+	if (defined($strParameter))
+	{
+		$strSql .= ",${strParameter}";
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = QuoteCSV($oData);
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = QuoteCSV(${$oTableHash}{&NAME});
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+	$strLogActual =~ s/SESSION,[0-9]+,[0-9]+,/SESSION,/g;
+	$strLogActual =~ s/OBJECT,[0-9]+,[0-9]+,/OBJECT,/g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   " -c log_statement=all" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pg_audit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pg_audit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pg_audit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "GRANT " .
+								(defined($strColumn) ?
+									lc(${strPrivilege}) ." (${strColumn})" :
+									uc(${strPrivilege})) .
+								" ON TABLE ${strObject} TO ${strRole} ");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "REVOKE  " . uc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " ON TABLE ${strObject} FROM ${strRole} ");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable;	   # Store table info for select, insert, update, delete
+my $strSql;		# Hold Sql commands
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+
+$strSql = 'CREATE  TABLE  public.test (id pg_catalog.int4   )' .
+		  '  WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+$strSql = 'drop table test';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			&COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Create some email friendly tests.  These first tests are session logging only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+
+PgSetUser('user1');
+
+$strSql = 'CREATE  TABLE  public.account (id pg_catalog.int4   ,' .
+		  ' name pg_catalog.text   COLLATE pg_catalog."default", ' .
+		  'password pg_catalog.text   COLLATE pg_catalog."default", '.
+		  'description pg_catalog.text   COLLATE pg_catalog."default")  '.
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pg_audit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+				'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pg_audit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+
+$strSql = 'CREATE SCHEMA  test ';
+PgLogExecute(COMMAND_CREATE_SCHEMA, $strSql, 'test');
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+
+$strSql = 'CREATE  TABLE  test.pg_class  WITH (oids=OFF)   AS SELECT relname,' .
+		  ' relnamespace, reltype, reloftype, relowner, relam, relfilenode, ' .
+		  'reltablespace, relpages, reltuples, relallvisible, reltoastrelid, ' .
+		  'relhasindex, relisshared, relpersistence, relkind, relnatts, ' .
+		  'relchecks, relhasoids, relhaspkey, relhasrules, relhastriggers, ' .
+		  'relhassubclass, relrowsecurity, relispopulated, relreplident, ' .
+		  'relfrozenxid, relminmxid, relacl, reloptions ' .
+		  'FROM pg_catalog.pg_class ';
+PgLogExecute(COMMAND_INSERT, $strSql, undef, true, false);
+PgLogExecute(COMMAND_CREATE_TABLE_AS, $strSql, 'test.pg_class', false, true);
+
+$strSql = "COPY test.pg_class from '" . abs_path($strTestPath) . "/class.out'";
+PgLogExecute(COMMAND_INSERT, $strSql);
+#PgLogExecute(COMMAND_COPY_FROM, $strSql, undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+			 'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+$strSql = 'CREATE  TABLE  test.test_insert (id pg_catalog.int4   )  ' .
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'test.test_insert');
+
+$strSql = 'PREPARE pgclassstmt (oid) as insert into test.test_insert (id) ' .
+		  'values ($1)';
+PgLogExecute(COMMAND_PREPARE_WRITE, $strSql);
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, "1");
+
+$strSql = 'EXECUTE pgclassstmt (1)';
+PgLogExecute(COMMAND_EXECUTE_WRITE, $strSql, undef, true, true);
+
+# Create a table with a primary key
+$strSql = 'CREATE  TABLE  public.test (id pg_catalog.int4   , ' .
+		  'name pg_catalog.text   COLLATE pg_catalog."default", description ' .
+		  'pg_catalog.text   COLLATE pg_catalog."default", CONSTRAINT ' .
+		  'test_pkey PRIMARY KEY (id))  WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_INDEX, $strSql, 'public.test_pkey', true, false);
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test', false, true);
+
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+$strSql = 'GRANT SELECT ON TABLE public.test TO PUBLIC ';
+PgLogExecute(COMMAND_GRANT, $strSql);
+
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+
+$strSql = 'GRANT select (name) ON TABLE public.test TO PUBLIC ';
+PgLogExecute(COMMAND_GRANT, $strSql);
+
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Try a select that does not reference any tables
+PgLogExecute(COMMAND_SELECT, 'select 1, current_timestamp');
+
+# Now try the same in a do block
+$strSql = 'do $$ declare test int; begin select 1 into test; end $$';
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'select 1';
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, true);
+
+# Insert some data into test and try a loop in a do block
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (1)');
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (2)');
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (3)');
+
+$strSql = 'do $$ ' .
+		  'declare ' .
+		  '	result record;' .
+		  'begin ' .
+		  '	for result in select id from test loop ' .
+		  '		insert into test (id) values (result.id + 100); ' .
+		  '	end loop; ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'select id from test';
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false);
+
+$strSql = 'insert into test (id) values (result.id + 100)';
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+# Test EXECUTE with bind
+$strSql = "select * from test where id = ?";
+my $hStatement = $hDb->prepare($strSql);
+
+$strSql = "select * from test where id = \$1";
+$hStatement->bind_param(1, 101);
+$hStatement->execute();
+
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false, undef, "101");
+
+$hStatement->bind_param(1, 103);
+$hStatement->execute();
+
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false, undef, "103");
+
+$hStatement->finish();
+
+# Now try some DDL in a do block
+$strSql = 'do $$ ' .
+		  'begin ' .
+		  '	create table test_block (id int); ' .
+		  '	drop table test_block; ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'CREATE  TABLE  public.test_block (id pg_catalog.int4   )  ' .
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test_block', false, false);
+
+$strSql = 'drop table test_block';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'public.test_block', false, false);
+
+# Generate an error in a do block and make sure the stack gets cleaned up
+$strSql = 'do $$ ' .
+		  'begin ' .
+		  '	create table bobus.test_block (id int); ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, undef, undef, undef, undef, true);
+# PgLogExecute(COMMAND_SELECT, 'select 1');
+# exit 0;
+
+# Try explain
+PgLogExecute(COMMAND_SELECT, 'explain select 1', undef, true, false);
+PgLogExecute(COMMAND_EXPLAIN, 'explain select 1', undef, false, true);
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+# Test alter and drop table statements
+$strSql = 'ALTER TABLE public.test DROP COLUMN description ';
+PgLogExecute(COMMAND_ALTER_TABLE_COLUMN,
+			 $strSql, 'public.test.description', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 $strSql, 'public.test', false, true);
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+$strSql = 'ALTER TABLE  public.test RENAME TO test2';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'public.test2');
+
+$strSql = 'ALTER TABLE public.test2 SET SCHEMA test';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'test.test2');
+
+$strSql = 'ALTER TABLE test.test2 ADD COLUMN description pg_catalog.text   ' .
+		  'COLLATE pg_catalog."default"';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'test.test2');
+
+$strSql = 'ALTER TABLE test.test2 DROP COLUMN description ';
+PgLogExecute(COMMAND_ALTER_TABLE_COLUMN, $strSql,
+			 'test.test2.description', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql,
+			 'test.test2', false, true);
+
+$strSql = 'drop table test.test2';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'test.test2', true, false);
+PgLogExecute(COMMAND_DROP_TABLE_CONSTRAINT, $strSql, 'test_pkey on test.test2',
+			 false, false);
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, $strSql, 'test.test_pkey', false, true);
+
+$strSql = "CREATE  FUNCTION public.int_add(IN a pg_catalog.int4 , IN b " .
+		  "pg_catalog.int4 ) RETURNS  pg_catalog.int4 LANGUAGE plpgsql  " .
+		  "VOLATILE  CALLED ON NULL INPUT SECURITY INVOKER COST 100   AS '" .
+		  " begin return a + b; end '";
+PgLogExecute(COMMAND_CREATE_FUNCTION, $strSql,
+			 'public.int_add(integer,integer)');
+PgLogExecute(COMMAND_SELECT, "select int_add(1, 1)",
+							 undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add', false, true);
+
+$strSql = "CREATE AGGREGATE public.sum_test(  pg_catalog.int4) " .
+		  "(SFUNC=public.int_add, STYPE=pg_catalog.int4, INITCOND='0')";
+PgLogExecute(COMMAND_CREATE_AGGREGATE, $strSql, 'public.sum_test(integer)');
+
+# There's a bug here in deparse:
+$strSql = "ALTER AGGREGATE public.sum_test(integer) RENAME TO sum_test2";
+PgLogExecute(COMMAND_ALTER_AGGREGATE, $strSql, 'public.sum_test2(integer)');
+
+$strSql = "CREATE COLLATION public.collation_test (LC_COLLATE = 'de_DE', " .
+		  "LC_CTYPE = 'de_DE')";
+PgLogExecute(COMMAND_CREATE_COLLATION, $strSql, 'public.collation_test');
+
+$strSql =  "ALTER COLLATION public.collation_test RENAME TO collation_test2";
+PgLogExecute(COMMAND_ALTER_COLLATION, $strSql, 'public.collation_test2');
+
+$strSql = "CREATE  CONVERSION public.conversion_test FOR 'SQL_ASCII' " .
+		  "TO 'MULE_INTERNAL' FROM pg_catalog.ascii_to_mic";
+PgLogExecute(COMMAND_CREATE_CONVERSION, $strSql, 'public.conversion_test');
+
+$strSql = "ALTER CONVERSION public.conversion_test RENAME TO conversion_test2";
+PgLogExecute(COMMAND_ALTER_CONVERSION, $strSql, 'public.conversion_test2');
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 89fff77..6b0b407 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -125,6 +125,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..f9152cd
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,335 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pgaudit" xreflabel="pgaudit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pgaudit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pg_audit.log</literal>
+        GUC. There are six classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>PARAMETER</literal> - Parameters that were passed for the statement.  Parameters immediately follow the statement text. 
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pg_audit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pg_audit.log = 'all, -misc'
+          </programlisting>
+      </para>
+      
+      <para>
+      Note that <literal>pg_audit.log</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pg_audit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pg_audit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+
+      <para>
+      Note that <literal>pg_audit.role</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Authors</title>
+
+    <para>
+      Abhijit Menon-Sen <email>[email protected]</email>, Ian Barwick <email>[email protected]</email>, and David Steele <email>[email protected]</email>.
+    </para>
+  </sect2>
+</sect1>


Attachments:

  [text/plain] pg_audit-v5.patch (101.1K, ../../[email protected]/2-pg_audit-v5.patch)
  download | inline diff:
diff --git a/contrib/Makefile b/contrib/Makefile
index 195d447..d8e75f4 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -29,6 +29,7 @@ SUBDIRS = \
 		pageinspect	\
 		passwordcheck	\
 		pg_archivecleanup \
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000..32bc6d9
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,20 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+
+DATA = pg_audit--1.0.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_audit
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_audit/pg_audit--1.0.0.sql b/contrib/pg_audit/pg_audit--1.0.0.sql
new file mode 100644
index 0000000..9d9ee83
--- /dev/null
+++ b/contrib/pg_audit/pg_audit--1.0.0.sql
@@ -0,0 +1,22 @@
+/* pg_audit/pg_audit--1.0.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_audit" to load this file.\quit
+
+CREATE FUNCTION pg_audit_ddl_command_end()
+	RETURNS event_trigger
+	LANGUAGE C
+	AS 'MODULE_PATHNAME', 'pg_audit_ddl_command_end';
+
+CREATE EVENT TRIGGER pg_audit_ddl_command_end
+	ON ddl_command_end
+	EXECUTE PROCEDURE pg_audit_ddl_command_end();
+
+CREATE FUNCTION pg_audit_sql_drop()
+	RETURNS event_trigger
+	LANGUAGE C
+	AS 'MODULE_PATHNAME', 'pg_audit_sql_drop';
+
+CREATE EVENT TRIGGER pg_audit_sql_drop
+	ON sql_drop
+	EXECUTE PROCEDURE pg_audit_sql_drop();
diff --git a/contrib/pg_audit/pg_audit.c b/contrib/pg_audit/pg_audit.c
new file mode 100644
index 0000000..65c8ed2
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1712 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An auditing extension for PostgreSQL. Improves on standard statement logging
+ * by adding more logging classes, object level logging, and providing
+ * fully-qualified object names for all DML and many DDL statements (See
+ * pg_audit.sgml for details).
+ *
+ * Copyright (c) 2014-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/pg_audit/pg_audit.c
+ *------------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_class.h"
+#include "catalog/namespace.h"
+#include "commands/dbcommands.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "libpq/auth.h"
+#include "nodes/nodes.h"
+#include "tcop/utility.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/*
+ * Event trigger prototypes
+ */
+Datum pg_audit_ddl_command_end(PG_FUNCTION_ARGS);
+Datum pg_audit_sql_drop(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(pg_audit_ddl_command_end);
+PG_FUNCTION_INFO_V1(pg_audit_sql_drop);
+
+/*
+ * auditRole is the string value of the pgaudit.role GUC, which contains the
+ * role for grant-based auditing.
+ */
+char *auditRole = NULL;
+
+/*
+ * auditLog is the string value of the pgaudit.log GUC, e.g. "read, write, ddl"
+ * (it's not used by the module but is required by DefineCustomStringVariable).
+ * Each token corresponds to a flag in enum LogClass below. We convert the list
+ * of tokens into a bitmap in auditLogBitmap for internal use.
+ */
+char *auditLog = NULL;
+static uint64 auditLogBitmap = 0;
+
+/*
+ * String constants for audit types - used when logging to distinguish session
+ * vs. object auditing.
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pgaudit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC			"MISC"
+#define CLASS_PARAMETER		"PARAMETER"
+#define CLASS_READ			"READ"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_ALL			"ALL"
+#define CLASS_NONE			"NONE"
+
+/* Log class enum used to represent bits in auditLogBitmap */
+enum LogClass
+{
+	LOG_NONE = 0,
+
+	/* DDL: CREATE/DROP/ALTER */
+	LOG_DDL = (1 << 1),
+
+	/* Function execution */
+	LOG_FUNCTION = (1 << 2),
+
+	/* Statements not covered by another class */
+	LOG_MISC = (1 << 3),
+
+	/* Function execution */
+	LOG_PARAMETER = (1 << 4),
+
+	/* SELECT */
+	LOG_READ = (1 << 5),
+
+	/* INSERT, UPDATE, DELETE, TRUNCATE */
+	LOG_WRITE = (1 << 6),
+
+	/* Absolutely everything */
+	LOG_ALL = ~(uint64)0
+};
+
+/* String constants for logging commands */
+#define COMMAND_DELETE		"DELETE"
+#define COMMAND_EXECUTE		"EXECUTE"
+#define COMMAND_INSERT		"INSERT"
+#define COMMAND_UPDATE		"UPDATE"
+#define COMMAND_SELECT		"SELECT"
+
+#define COMMAND_UNKNOWN		"UNKNOWN"
+
+/* String constants for logging object types */
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_TOASTVALUE		"TOASTVALUE"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object. If a statement affects multiple objects multiple AuditEvents must be
+ * created to represent it.
+ */
+typedef struct
+{
+	int64 statementId;
+	int64 substatementId;
+
+	LogStmtLevel logStmtLevel;
+	NodeTag commandTag;
+	const char *command;
+	const char *objectType;
+	char *objectName;
+	const char *commandText;
+	ParamListInfo paramList;
+
+	bool granted;
+	bool logged;
+} AuditEvent;
+
+/*
+ * A simple FIFO queue to keep track of the current stack of audit events.
+ */
+typedef struct AuditEventStackItem
+{
+	struct AuditEventStackItem *next;
+
+	AuditEvent auditEvent;
+
+	MemoryContext contextAudit;
+	MemoryContextCallback contextCallback;
+} AuditEventStackItem;
+
+AuditEventStackItem *auditEventStack = NULL;
+
+/*
+ * Track when an internal statement is running so it is not logged
+ */
+static bool internalStatement = false;
+
+/*
+ * Track running total for statements and substatements and whether or not
+ * anything has been logged since this statement began.
+ */
+static uint64 statementTotal = 0;
+static uint64 substatementTotal = 0;
+
+static bool statementLogged = false;
+
+/*
+ * Stack functions
+ *
+ * Audit events can go down to multiple levels so a stack is maintained to keep
+ * track of them.
+ */
+
+/*
+ * Respond to callbacks registered with MemoryContextRegisterResetCallback().
+ * Removes the event(s) off the stack that have become obsolete once the
+ * MemoryContext has been freed.  The callback should always be freeing the top
+ * of the stack, but the code is tolerant of out-of-order callbacks.
+ */
+static void
+stack_free(void *stackFree)
+{
+	AuditEventStackItem *nextItem = auditEventStack;
+
+	/* Only process if the stack contains items */
+	while (nextItem != NULL)
+	{
+		/* Check if this item matches the item to be freed */
+		if (nextItem == (AuditEventStackItem *)stackFree)
+		{
+			/* Move top of stack the the item after the freed item */
+			auditEventStack = nextItem->next;
+
+			/* If the stack is not empty */
+			if (auditEventStack == NULL)
+			{
+				/* Reset internal statement in case of error */
+				internalStatement = false;
+
+				/* Reset sub statement total */
+				substatementTotal = 0;
+
+				/* Reset statement logged flag total */
+				statementLogged = false;
+			}
+
+			return;
+		}
+
+		/* Still looking, test the next item */
+		nextItem = nextItem->next;
+	}
+}
+
+/*
+ * Push a new audit event onto the stack and create a new memory context to
+ * store it.
+ */
+static AuditEventStackItem *
+stack_push()
+{
+	MemoryContext contextAudit;
+	MemoryContext contextOld;
+	AuditEventStackItem *stackItem;
+
+	/* Create a new memory context */
+	contextAudit = AllocSetContextCreate(CurrentMemoryContext,
+										 "pg_audit stack context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+	contextOld = MemoryContextSwitchTo(contextAudit);
+
+	/* Allocate the stack item */
+	stackItem = palloc0(sizeof(AuditEventStackItem));
+
+	/* Store memory contexts */
+	stackItem->contextAudit = contextAudit;
+
+	/* If item already on stack then push it down */
+	if (auditEventStack != NULL)
+		stackItem->next = auditEventStack;
+	else
+		stackItem->next = NULL;
+
+	/*
+	 * Setup a callback in case an error happens.  stack_free() will truncate
+	 * the stack at this item.
+	 */
+	stackItem->contextCallback.func = stack_free;
+	stackItem->contextCallback.arg = (void *)stackItem;
+	MemoryContextRegisterResetCallback(contextAudit,
+									   &stackItem->contextCallback);
+
+	/* Push item on the stack */
+	auditEventStack = stackItem;
+
+	/* Return to the old memory context */
+	MemoryContextSwitchTo(contextOld);
+
+	/* Return the stack item */
+	return stackItem;
+}
+
+/*
+ * Pop an audit event from the stack by deleting the memory context that
+ * contains it.  The callback to stack_free() does the actual pop.
+ */
+static void
+stack_pop()
+{
+	/* Error if the stack is already empty */
+	if (auditEventStack == NULL)
+		elog(ERROR, "pg_audit stack is already empty");
+
+	/* Switch the old memory context and delete the audit context */
+	MemoryContextDelete(auditEventStack->contextAudit);
+}
+
+/*
+ * Takes an AuditEvent and returns true or false depending on whether the event
+ * should be logged according to the pgaudit.roles/log settings. If it returns
+ * true, also fills in the name of the LogClass which it is logged under.
+ */
+static bool
+log_check(AuditEvent *e, const char **classname)
+{
+	enum LogClass class = LOG_NONE;
+
+	/* By default put everything in the MISC class. */
+	*classname = CLASS_MISC;
+	class = LOG_MISC;
+
+	/*
+	 * Look at the type of the command and decide what LogClass needs to be
+	 * enabled for the command to be logged.
+	 */
+	switch (e->logStmtLevel)
+	{
+		case LOGSTMT_MOD:
+			*classname = CLASS_WRITE;
+			class = LOG_WRITE;
+			break;
+
+		case LOGSTMT_DDL:
+			*classname = CLASS_DDL;
+			class = LOG_DDL;
+
+		case LOGSTMT_ALL:
+			switch (e->commandTag)
+			{
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+				case T_ExecuteStmt:
+					*classname = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				case T_VacuumStmt:
+				case T_ReindexStmt:
+					*classname = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				case T_DoStmt:
+					*classname = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * We log audit events under the following conditions:
+	 *
+	 * 1. If the audit role has been explicitly granted permission for
+	 *    an operation.
+	 */
+	if (e->granted)
+	{
+		return true;
+	}
+
+	/* 2. If the event belongs to a class covered by pgaudit.log. */
+	if ((auditLogBitmap & class) == class)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Appends a properly quoted CSV field to StringInfo.
+ */
+static void
+append_valid_csv(StringInfoData *buffer, const char *appendStr)
+{
+	const char *pChar;
+
+	/*
+	 * If the append string is null then return.  NULL fields are not quoted
+	 * in CSV
+	 */
+	if (appendStr == NULL)
+		return;
+
+	/* Only format for CSV if appendStr contains: ", comma, \n, \r */
+	if (strstr(appendStr, ",") || strstr(appendStr, "\"") ||
+		strstr(appendStr, "\n") || strstr(appendStr, "\r"))
+	{
+		appendStringInfoCharMacro(buffer, '"');
+
+		for (pChar = appendStr; *pChar; pChar++)
+		{
+			if (*pChar == '"') /* double single quotes */
+				appendStringInfoCharMacro(buffer, *pChar);
+
+			appendStringInfoCharMacro(buffer, *pChar);
+		}
+
+		appendStringInfoCharMacro(buffer, '"');
+	}
+	/* Else just append */
+	else
+	{
+		appendStringInfoString(buffer, appendStr);
+	}
+}
+
+/*
+ * Takes an AuditEvent and, if it log_check(), writes it to the audit log. The
+ * AuditEvent is assumed to be completely filled in by the caller (unknown
+ * values must be set to "" so that they can be logged without error checking).
+ */
+static void
+log_audit_event(AuditEventStackItem *stackItem)
+{
+	const char *classname;
+	MemoryContext contextOld;
+	StringInfoData auditStr;
+
+	/* Check that this event should be logged. */
+	if (!log_check(&stackItem->auditEvent, &classname))
+		return;
+
+	/* Use audit memory context in case something is not freed */
+	contextOld = MemoryContextSwitchTo(stackItem->contextAudit);
+
+	/* Set statement and substatement Ids */
+	if (stackItem->auditEvent.statementId == 0)
+	{
+		/* If nothing has been logged yet then create a new statement Id */
+		if (!statementLogged)
+		{
+			statementTotal++;
+			statementLogged = true;
+		}
+
+		stackItem->auditEvent.statementId = statementTotal;
+		stackItem->auditEvent.substatementId = ++substatementTotal;
+	}
+
+	/* Create the audit string */
+	initStringInfo(&auditStr);
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.command);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.objectType);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.objectName);
+	appendStringInfoCharMacro(&auditStr, ',');
+
+	append_valid_csv(&auditStr, stackItem->auditEvent.commandText);
+
+	/* If parameter logging is turned on and there are parameters to log */
+	if (auditLogBitmap & LOG_PARAMETER &&
+		stackItem->auditEvent.paramList != NULL &&
+		stackItem->auditEvent.paramList->numParams > 0 &&
+		!IsAbortedTransactionBlockState())
+	{
+		ParamListInfo paramList = stackItem->auditEvent.paramList;
+		int paramIdx;
+
+		/* Iterate through all params */
+		for (paramIdx = 0; paramIdx < paramList->numParams; paramIdx++)
+		{
+			ParamExternData *prm = &paramList->params[paramIdx];
+			Oid 			 typeOutput;
+			bool 			 typeIsVarLena;
+			char 			*paramStr;
+
+			/* Add a comma for each param */
+			appendStringInfoCharMacro(&auditStr, ',');
+
+			/* Skip this param if null or if oid is invalid */
+			if (prm->isnull || !OidIsValid(prm->ptype))
+			{
+				continue;
+			}
+
+			/* Output the string */
+			getTypeOutputInfo(prm->ptype, &typeOutput, &typeIsVarLena);
+			paramStr = OidOutputFunctionCall(typeOutput, prm->value);
+
+			append_valid_csv(&auditStr, paramStr);
+			pfree(paramStr);
+		}
+	}
+
+	/* Log the audit string */
+	ereport(LOG,
+		(errmsg("AUDIT: %s,%ld,%ld,%s,%s",
+			stackItem->auditEvent.granted ?
+				AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+			stackItem->auditEvent.statementId,
+			stackItem->auditEvent.substatementId,
+			classname, auditStr.data),
+		 errhidestmt(true)));
+
+	/* Mark the audit event as logged */
+	stackItem->auditEvent.logged = true;
+
+	/* Switch back to the old memory context */
+	MemoryContextSwitchTo(contextOld);
+}
+
+/*
+ * Check if the role or any inherited role has any permission in the mask.  The
+ * public role is excluded from this check and superuser permissions are not
+ * considered.
+ */
+static bool
+log_acl_check(Datum aclDatum, Oid auditOid, AclMode mask)
+{
+	bool		result = false;
+	Acl		   *acl;
+	AclItem	   *aclItemData;
+	int			aclIndex;
+	int			aclTotal;
+
+	/* Detoast column's ACL if necessary */
+	acl = DatumGetAclP(aclDatum);
+
+	/* Get the acl list and total */
+	aclTotal = ACL_NUM(acl);
+	aclItemData = ACL_DAT(acl);
+
+	/* Check privileges granted directly to auditOid */
+	for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+	{
+		AclItem *aclItem = &aclItemData[aclIndex];
+
+		if (aclItem->ai_grantee == auditOid &&
+			aclItem->ai_privs & mask)
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/*
+	 * Check privileges granted indirectly via role memberships. We do this in
+	 * a separate pass to minimize expensive indirect membership tests.  In
+	 * particular, it's worth testing whether a given ACL entry grants any
+	 * privileges still of interest before we perform the has_privs_of_role
+	 * test.
+	 */
+	if (!result)
+	{
+		for (aclIndex = 0; aclIndex < aclTotal; aclIndex++)
+		{
+			AclItem *aclItem = &aclItemData[aclIndex];
+
+			/* Don't test public or auditOid (it has been tested already) */
+			if (aclItem->ai_grantee == ACL_ID_PUBLIC ||
+				aclItem->ai_grantee == auditOid)
+				continue;
+
+			/*
+			 * Check that the role has the required privileges and that it is
+			 * inherited by auditOid.
+			 */
+			if (aclItem->ai_privs & mask &&
+				has_privs_of_role(auditOid, aclItem->ai_grantee))
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a relation.
+ */
+static bool
+log_relation_check(Oid relOid,
+				   Oid auditOid,
+				   AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get relation tuple from pg_class */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+	/* Return false if tuple is not valid */
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* If not null then test */
+	if (!isNull)
+		result = log_acl_check(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute.
+ */
+static bool
+log_attribute_check(Oid relOid,
+					AttrNumber attNum,
+					Oid auditOid,
+					AclMode mask)
+{
+	bool		result = false;
+	HeapTuple	attTuple;
+	Datum		aclDatum;
+	bool		isNull;
+
+	/* Get the attribute's ACL */
+	attTuple = SearchSysCache2(ATTNUM,
+							   ObjectIdGetDatum(relOid),
+							   Int16GetDatum(attNum));
+
+	/* Return false if attribute is invalid */
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only process attribute that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = log_acl_check(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on an attribute in
+ * the provided set.  If the set is empty, then all valid attributes in the
+ * relation will be tested.
+ */
+static bool
+log_attribute_check_any(Oid relOid,
+						Oid auditOid,
+						Bitmapset *attributeSet,
+						AclMode mode)
+{
+	bool result = false;
+	AttrNumber col;
+	Bitmapset *tmpSet;
+
+	/* If bms is empty then check for any column match */
+	if (bms_is_empty(attributeSet))
+	{
+		HeapTuple	classTuple;
+		AttrNumber	nattrs;
+		AttrNumber	curr_att;
+
+		/* Get relation to determine total attribute */
+		classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+
+		if (!HeapTupleIsValid(classTuple))
+			return false;
+
+		nattrs = ((Form_pg_class) GETSTRUCT(classTuple))->relnatts;
+		ReleaseSysCache(classTuple);
+
+		/* Check each column */
+		for (curr_att = 1; curr_att <= nattrs; curr_att++)
+		{
+			if (log_attribute_check(relOid, curr_att, auditOid, mode))
+				return true;
+		}
+	}
+
+	/* bms_first_member is destructive, so make a copy before using it. */
+	tmpSet = bms_copy(attributeSet);
+
+	/* Check each column */
+	while ((col = bms_first_member(tmpSet)) >= 0)
+	{
+		col += FirstLowInvalidHeapAttributeNumber;
+
+		if (col != InvalidAttrNumber &&
+			log_attribute_check(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	/* Free the column set */
+	bms_free(tmpSet);
+
+	return result;
+}
+
+/*
+ * Create AuditEvents for SELECT/DML operations via executor permissions checks.
+ */
+static void
+log_select_dml(Oid auditOid, List *rangeTabls)
+{
+	ListCell *lr;
+	bool first = true;
+	bool found = false;
+
+	/* Do not log if this is an internal statement */
+	if (internalStatement)
+		return;
+
+	foreach(lr, rangeTabls)
+	{
+		Oid relOid;
+		Relation rel;
+		RangeTblEntry *rte = lfirst(lr);
+
+		/* We only care about tables, and can ignore subqueries etc. */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		found = true;
+
+		/*
+		 * Filter out any system relations
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			continue;
+		}
+
+		/*
+		 * We don't have access to the parsetree here, so we have to generate
+		 * the node type, object type, and command tag by decoding
+		 * rte->requiredPerms and rte->relkind.
+		 */
+		if (rte->requiredPerms & ACL_INSERT)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_InsertStmt;
+			auditEventStack->auditEvent.command = COMMAND_INSERT;
+		}
+		else if (rte->requiredPerms & ACL_UPDATE)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_UpdateStmt;
+			auditEventStack->auditEvent.command = COMMAND_UPDATE;
+		}
+		else if (rte->requiredPerms & ACL_DELETE)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_MOD;
+			auditEventStack->auditEvent.commandTag = T_DeleteStmt;
+			auditEventStack->auditEvent.command = COMMAND_DELETE;
+		}
+		else if (rte->requiredPerms & ACL_SELECT)
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEventStack->auditEvent.commandTag = T_SelectStmt;
+			auditEventStack->auditEvent.command = COMMAND_SELECT;
+		}
+		else
+		{
+			auditEventStack->auditEvent.logStmtLevel = LOGSTMT_ALL;
+			auditEventStack->auditEvent.commandTag = T_Invalid;
+			auditEventStack->auditEvent.command = COMMAND_UNKNOWN;
+		}
+
+		/*
+		 * Fill values in the event struct that are required for session
+		 * logging.
+		 */
+		auditEventStack->auditEvent.granted = false;
+
+		/* If this is the first rte then session log */
+		if (first)
+		{
+			auditEventStack->auditEvent.objectName = "";
+			auditEventStack->auditEvent.objectType = "";
+
+			log_audit_event(auditEventStack);
+
+			first = false;
+		}
+
+		/* Get the relation type */
+		switch (rte->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_TOASTVALUE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TOASTVALUE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_MATVIEW;
+				break;
+
+			default:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_UNKNOWN;
+				break;
+		}
+
+		/* Get the relation name */
+		auditEventStack->auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Perform object auditing only if the audit role is valid */
+		if (auditOid != InvalidOid)
+		{
+			AclMode auditPerms = (ACL_SELECT | ACL_UPDATE | ACL_INSERT) &
+								 rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (log_relation_check(relOid, auditOid, auditPerms))
+			{
+				auditEventStack->auditEvent.granted = true;
+			}
+
+			/*
+			 * Else check if the audit role has column-level permissions for
+			 * select, insert, or update.
+			 */
+			else if (auditPerms != 0)
+			{
+				/*
+				 * Check the select columns to see if the audit role has
+				 * priveleges on any of them.
+				 */
+				if (auditPerms & ACL_SELECT)
+				{
+					auditEventStack->auditEvent.granted =
+						log_attribute_check_any(relOid, auditOid,
+												rte->selectedCols,
+												ACL_SELECT);
+				}
+
+				/*
+				 * Check the modified columns to see if the audit role has
+				 * privileges on any of them.
+				 */
+				if (!auditEventStack->auditEvent.granted)
+				{
+					auditPerms &= (ACL_INSERT | ACL_UPDATE);
+
+					if (auditPerms)
+					{
+						auditEventStack->auditEvent.granted =
+							log_attribute_check_any(relOid, auditOid,
+													rte->modifiedCols,
+													auditPerms);
+					}
+				}
+			}
+		}
+
+		/* Only do relation level logging if a grant was found. */
+		if (auditEventStack->auditEvent.granted)
+		{
+			auditEventStack->auditEvent.logged = false;
+			log_audit_event(auditEventStack);
+		}
+
+		pfree(auditEventStack->auditEvent.objectName);
+	}
+
+	/*
+	 * If no tables were found that means that RangeTbls was empty or all
+	 * relations were in the system schema.  In that case still log a
+	 * session record.
+	 */
+	if (!found)
+	{
+		auditEventStack->auditEvent.granted = false;
+		auditEventStack->auditEvent.logged = false;
+
+		log_audit_event(auditEventStack);
+	}
+}
+
+/*
+ * Create AuditEvents for certain kinds of CREATE, ALTER, and DELETE statements
+ * where the object can be logged.
+ */
+static void
+log_create_alter_drop(Oid classId,
+					  Oid objectId)
+{
+	/* Only perform when class is relation */
+	if (classId == RelationRelationId)
+	{
+		Relation rel;
+		Form_pg_class class;
+
+		/* Open the relation */
+		rel = relation_open(objectId, NoLock);
+
+		/* Filter out any system relations */
+		if (IsToastNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			return;
+		}
+
+		/* Get rel information and close it */
+		class = RelationGetForm(rel);
+		auditEventStack->auditEvent.objectName =
+			quote_qualified_identifier(get_namespace_name(
+									   RelationGetNamespace(rel)),
+									   RelationGetRelationName(rel));
+		relation_close(rel, NoLock);
+
+		/* Set object type based on relkind */
+		switch (class->relkind)
+		{
+			case RELKIND_RELATION:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_TABLE;
+				break;
+
+			case RELKIND_INDEX:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_INDEX;
+				break;
+
+			case RELKIND_SEQUENCE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_SEQUENCE;
+				break;
+
+			case RELKIND_VIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_VIEW;
+				break;
+
+			case RELKIND_COMPOSITE_TYPE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_COMPOSITE_TYPE;
+				break;
+
+			case RELKIND_FOREIGN_TABLE:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_FOREIGN_TABLE;
+				break;
+
+			case RELKIND_MATVIEW:
+				auditEventStack->auditEvent.objectType =
+					OBJECT_TYPE_MATVIEW;
+				break;
+
+			/*
+			 * Any other cases will be handled by log_utility_command().
+			 */
+			default:
+				return;
+				break;
+		}
+	}
+}
+
+/*
+ * Create AuditEvents for non-catalog function execution, as detected by
+ * log_object_access() below.
+ */
+static void
+log_function_execute(Oid objectId)
+{
+	HeapTuple proctup;
+	Form_pg_proc proc;
+	AuditEventStackItem *stackItem;
+
+	/* Get info about the function. */
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(objectId));
+
+	if (!proctup)
+		elog(ERROR, "cache lookup failed for function %u", objectId);
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+	/*
+	 * Logging execution of all pg_catalog functions would make the log
+	 * unusably noisy.
+	 */
+	if (IsSystemNamespace(proc->pronamespace))
+	{
+		ReleaseSysCache(proctup);
+		return;
+	}
+
+	/* Push audit event onto the stack */
+	stackItem = stack_push();
+
+	/* Generate the fully-qualified function name. */
+	stackItem->auditEvent.objectName =
+		quote_qualified_identifier(get_namespace_name(proc->pronamespace),
+								   NameStr(proc->proname));
+	ReleaseSysCache(proctup);
+
+	/* Log the function call */
+	stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+	stackItem->auditEvent.commandTag = T_DoStmt;
+	stackItem->auditEvent.command = COMMAND_EXECUTE;
+	stackItem->auditEvent.objectType = OBJECT_TYPE_FUNCTION;
+	stackItem->auditEvent.commandText = stackItem->next->auditEvent.commandText;
+
+	log_audit_event(stackItem);
+
+	/* Pop audit event from the stack */
+	stack_pop();
+}
+
+/*
+ * Log object accesses (which is more about DDL than DML, even though it
+ * sounds like the latter).
+ */
+static void
+log_object_access(ObjectAccessType access,
+				  Oid classId,
+				  Oid objectId,
+				  int subId,
+				  void *arg)
+{
+	switch (access)
+	{
+		/* Log execute */
+		case OAT_FUNCTION_EXECUTE:
+			if (auditLogBitmap & LOG_FUNCTION)
+				log_function_execute(objectId);
+			break;
+
+		/* Log create */
+		case OAT_POST_CREATE:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessPostCreate *pc = arg;
+
+				if (pc->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log alter */
+		case OAT_POST_ALTER:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessPostAlter *pa = arg;
+
+				if (pa->is_internal)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* Log drop */
+		case OAT_DROP:
+			if (auditLogBitmap & LOG_DDL)
+			{
+				ObjectAccessDrop *drop = arg;
+
+				if (drop->dropflags & PERFORM_DELETION_INTERNAL)
+					return;
+
+				log_create_alter_drop(classId, objectId);
+			}
+			break;
+
+		/* All others processed by log_utility_command() */
+		default:
+			break;
+	}
+}
+
+/*
+ * Hook functions
+ */
+static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
+static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static object_access_hook_type next_object_access_hook = NULL;
+static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
+static ExecutorEnd_hook_type next_ExecutorEnd_hook = NULL;
+
+/*
+ * Hook ExecutorStart to get the query text and basic command type for queries
+ * that do not contain a table so can't be idenitified accurately in
+ * ExecutorCheckPerms.
+ */
+static void
+pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	if (!internalStatement)
+	{
+		/* Allocate the audit event */
+		stackItem = stack_push();
+
+		/* Initialize command */
+		switch (queryDesc->operation)
+		{
+			case CMD_SELECT:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+				stackItem->auditEvent.commandTag = T_SelectStmt;
+				stackItem->auditEvent.command = COMMAND_SELECT;
+				break;
+
+			case CMD_INSERT:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_InsertStmt;
+				stackItem->auditEvent.command = COMMAND_INSERT;
+				break;
+
+			case CMD_UPDATE:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_UpdateStmt;
+				stackItem->auditEvent.command = COMMAND_UPDATE;
+				break;
+
+			case CMD_DELETE:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;
+				stackItem->auditEvent.commandTag = T_DeleteStmt;
+				stackItem->auditEvent.command = COMMAND_DELETE;
+				break;
+
+			default:
+				stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;
+				stackItem->auditEvent.commandTag = T_Invalid;
+				stackItem->auditEvent.command = COMMAND_UNKNOWN;
+				break;
+		}
+
+		/* Initialize the audit event */
+		stackItem->auditEvent.objectName = "";
+		stackItem->auditEvent.objectType = "";
+		stackItem->auditEvent.commandText = queryDesc->sourceText;
+		stackItem->auditEvent.paramList = queryDesc->params;
+	}
+
+	/* Call the previous hook or standard function */
+	if (next_ExecutorStart_hook)
+		next_ExecutorStart_hook(queryDesc, eflags);
+	else
+		standard_ExecutorStart(queryDesc, eflags);
+}
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
+{
+	Oid auditOid;
+
+	/* Get the audit oid if the role exists. */
+	auditOid = get_role_oid(auditRole, true);
+
+	/* Log DML if the audit role is valid or session logging is enabled. */
+	if ((auditOid != InvalidOid || auditLogBitmap != 0) &&
+		!IsAbortedTransactionBlockState())
+		log_select_dml(auditOid, rangeTabls);
+
+	/* Call the next hook function. */
+	if (next_ExecutorCheckPerms_hook &&
+		!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))
+		return false;
+
+	return true;
+}
+
+/*
+ * Hook ExecutorEnd to pop statement audit event off the stack.
+ */
+static void
+pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
+{
+	/* Call the next hook or standard function */
+	if (next_ExecutorEnd_hook)
+		next_ExecutorEnd_hook(queryDesc);
+	else
+		standard_ExecutorEnd(queryDesc);
+
+	/* Pop the audit event off the stack */
+	if (!internalStatement)
+	{
+		stack_pop();
+	}
+}
+
+/*
+ * Hook ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pgaudit_ProcessUtility_hook(Node *parsetree,
+							const char *queryString,
+							ProcessUtilityContext context,
+							ParamListInfo params,
+							DestReceiver *dest,
+							char *completionTag)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	/* Allocate the audit event */
+	if (!IsAbortedTransactionBlockState())
+	{
+		/* Process top level utility statement */
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			if (auditEventStack != NULL)
+				elog(ERROR, "pg_audit stack is not empty");
+
+			/* Set params */
+			stackItem = stack_push();
+			stackItem->auditEvent.paramList = params;
+		}
+		else
+			stackItem = stack_push();
+
+		stackItem->auditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+		stackItem->auditEvent.commandTag = nodeTag(parsetree);
+		stackItem->auditEvent.command = CreateCommandTag(parsetree);
+		stackItem->auditEvent.objectName = "";
+		stackItem->auditEvent.objectType = "";
+		stackItem->auditEvent.commandText = queryString;
+
+		/*
+		 * If this is a DO block log it before calling the next ProcessUtility
+		 * hook.
+		 */
+		if (auditLogBitmap != 0 &&
+			stackItem->auditEvent.commandTag == T_DoStmt &&
+			!IsAbortedTransactionBlockState())
+		{
+			log_audit_event(stackItem);
+		}
+	}
+
+	/* Call the standard process utility chain. */
+	if (next_ProcessUtility_hook)
+		(*next_ProcessUtility_hook) (parsetree, queryString, context,
+									 params, dest, completionTag);
+	else
+		standard_ProcessUtility(parsetree, queryString, context,
+								params, dest, completionTag);
+
+	/* Process the audit event if there is one. */
+	if (stackItem != NULL)
+	{
+		/* Log the utility command if logging is on, the command has not already
+		 * been logged by another hook, and the transaction is not aborted. */
+		if (auditLogBitmap != 0 && !stackItem->auditEvent.logged &&
+			!IsAbortedTransactionBlockState())
+			log_audit_event(stackItem);
+
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			while (auditEventStack != NULL)
+				stack_pop();
+		}
+		else
+			stack_pop();
+	}
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for execute,
+ * create, drop, and alter commands.  Most of the audit information is filled in
+ * by log_utility_command().
+ */
+static void
+pgaudit_object_access_hook(ObjectAccessType access,
+						   Oid classId,
+						   Oid objectId,
+						   int subId,
+						   void *arg)
+{
+	if (auditLogBitmap != 0 && !IsAbortedTransactionBlockState() &&
+		auditLogBitmap & (LOG_DDL | LOG_FUNCTION))
+		log_object_access(access, classId, objectId, subId, arg);
+
+	if (next_object_access_hook)
+		(*next_object_access_hook) (access, classId, objectId, subId, arg);
+}
+
+/*
+ * Event trigger functions
+ */
+
+/*
+ * Supply additional data for (non drop) statements that have event trigger
+ * support and can be deparsed.
+ */
+Datum
+pg_audit_ddl_command_end(PG_FUNCTION_ARGS)
+{
+	/* Continue only if session logging is enabled */
+	if (auditLogBitmap != LOG_DDL)
+	{
+		EventTriggerData *eventData;
+		int				  result, row;
+		TupleDesc		  spiTupDesc;
+		const char		 *query;
+		MemoryContext 	  contextQuery;
+		MemoryContext 	  contextOld;
+
+		/* This is an internal statement - do not log it */
+		internalStatement = true;
+
+		/* Make sure the fuction was fired as a trigger */
+		if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+			elog(ERROR, "not fired by event trigger manager");
+
+		/* Switch memory context */
+		contextQuery = AllocSetContextCreate(
+						CurrentMemoryContext,
+						"pgaudit_func_ddl_command_end temporary context",
+						ALLOCSET_DEFAULT_MINSIZE,
+						ALLOCSET_DEFAULT_INITSIZE,
+						ALLOCSET_DEFAULT_MAXSIZE);
+		contextOld = MemoryContextSwitchTo(contextQuery);
+
+		/* Get information about triggered events */
+		eventData = (EventTriggerData *) fcinfo->context;
+
+		/* Return objects affected by the (non drop) DDL statement */
+		query = "SELECT classid, objid, objsubid, UPPER(object_type), schema,\n"
+				"       identity, command\n"
+				"  FROM pg_event_trigger_get_creation_commands()";
+
+		/* Attempt to connect */
+		result = SPI_connect();
+
+		if (result < 0)
+			elog(ERROR, "pg_audit_ddl_command_end: SPI_connect returned %d",
+						result);
+
+		/* Execute the query */
+		result = SPI_execute(query, true, 0);
+
+		if (result != SPI_OK_SELECT)
+			elog(ERROR, "pg_audit_ddl_command_end: SPI_execute returned %d",
+						result);
+
+		/* Iterate returned rows */
+		spiTupDesc = SPI_tuptable->tupdesc;
+
+		for (row = 0; row < SPI_processed; row++)
+		{
+			HeapTuple  spiTuple;
+			bool	   isNull;
+
+			spiTuple = SPI_tuptable->vals[row];
+
+			/* Supply addition data to current audit event */
+			auditEventStack->auditEvent.logStmtLevel =
+				GetCommandLogLevel(eventData->parsetree);
+			auditEventStack->auditEvent.commandTag =
+				nodeTag(eventData->parsetree);
+			auditEventStack->auditEvent.command =
+				CreateCommandTag(eventData->parsetree);
+			auditEventStack->auditEvent.objectName =
+				SPI_getvalue(spiTuple, spiTupDesc, 6);
+			auditEventStack->auditEvent.objectType =
+				SPI_getvalue(spiTuple, spiTupDesc, 4);
+			auditEventStack->auditEvent.commandText =
+				TextDatumGetCString(
+					DirectFunctionCall1(pg_event_trigger_expand_command,
+										SPI_getbinval(spiTuple, spiTupDesc,
+													  7, &isNull)));
+
+			/* Log the audit event */
+			log_audit_event(auditEventStack);
+		}
+
+		/* Complete the query */
+		SPI_finish();
+
+		/* Switch to the old memory context */
+		MemoryContextSwitchTo(contextOld);
+		MemoryContextDelete(contextQuery);
+
+		/* No longer in an internal statement */
+		internalStatement = false;
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Supply additional data for drop statements that have event trigger support.
+ */
+Datum
+pg_audit_sql_drop(PG_FUNCTION_ARGS)
+{
+	if (auditLogBitmap & LOG_DDL)
+	{
+		int				  result, row;
+		TupleDesc		  spiTupDesc;
+		const char		 *query;
+		MemoryContext 	  contextQuery;
+		MemoryContext 	  contextOld;
+
+		/* This is an internal statement - do not log it */
+		internalStatement = true;
+
+		/* Make sure the fuction was fired as a trigger */
+		if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+			elog(ERROR, "not fired by event trigger manager");
+
+		/* Switch memory context */
+		contextQuery = AllocSetContextCreate(
+						CurrentMemoryContext,
+						"pgaudit_func_ddl_command_end temporary context",
+						ALLOCSET_DEFAULT_MINSIZE,
+						ALLOCSET_DEFAULT_INITSIZE,
+						ALLOCSET_DEFAULT_MAXSIZE);
+		contextOld = MemoryContextSwitchTo(contextQuery);
+
+		/* Return objects affected by the drop statement */
+		query = "SELECT classid, objid, objsubid, UPPER(object_type),\n"
+				"       schema_name, object_name, object_identity\n"
+				"  FROM pg_event_trigger_dropped_objects()";
+
+		/* Attempt to connect */
+		result = SPI_connect();
+
+		if (result < 0)
+			elog(ERROR, "pg_audit_ddl_drop: SPI_connect returned %d",
+						result);
+
+		/* Execute the query */
+		result = SPI_execute(query, true, 0);
+
+		if (result != SPI_OK_SELECT)
+			elog(ERROR, "pg_audit_ddl_drop: SPI_execute returned %d",
+						result);
+
+		/* Iterate returned rows */
+		spiTupDesc = SPI_tuptable->tupdesc;
+
+		for (row = 0; row < SPI_processed; row++)
+		{
+			HeapTuple  spiTuple;
+			char *schemaName;
+
+			spiTuple = SPI_tuptable->vals[row];
+
+			auditEventStack->auditEvent.objectType =
+				SPI_getvalue(spiTuple, spiTupDesc, 4);
+			schemaName = SPI_getvalue(spiTuple, spiTupDesc, 5);
+
+			if (!(pg_strcasecmp(auditEventStack->auditEvent.objectType,
+							"TYPE") == 0 ||
+				  pg_strcasecmp(schemaName, "pg_toast") == 0))
+			{
+				auditEventStack->auditEvent.objectName =
+						SPI_getvalue(spiTuple, spiTupDesc, 7);
+
+				log_audit_event(auditEventStack);
+			}
+		}
+
+		/* Complete the query */
+		SPI_finish();
+
+		/* Switch to the old memory context */
+		MemoryContextSwitchTo(contextOld);
+		MemoryContextDelete(contextQuery);
+
+		/* No longer in an internal statement */
+		internalStatement = false;
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * GUC check and assign functions
+ */
+
+/*
+ * Take a pg_audit.log value such as "read, write, dml", verify that each of the
+ * comma-separated tokens corresponds to a LogClass value, and convert them into
+ * a bitmap that log_audit_event can check.
+ */
+static bool
+check_pgaudit_log(char **newval, void **extra, GucSource source)
+{
+	List *flags;
+	char *rawval;
+	ListCell *lt;
+	uint64 *f;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawval = pstrdup(*newval);
+	if (!SplitIdentifierString(rawval, ',', &flags))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flags);
+		pfree(rawval);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated uint64 *f.
+	 */
+	f = (uint64 *) malloc(sizeof(uint64));
+	if (!f)
+		return false;
+	*f = 0;
+
+	foreach(lt, flags)
+	{
+		bool subtract = false;
+		uint64 class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then then token is subtractive. */
+		if (strstr(token, "-") == token)
+		{
+			token = token + 1;
+			subtract = true;
+		}
+
+		/* Test each token. */
+		if (pg_strcasecmp(token, CLASS_NONE) == 0)
+			class = LOG_NONE;
+		else if (pg_strcasecmp(token, CLASS_ALL) == 0)
+			class = LOG_ALL;
+		else if (pg_strcasecmp(token, CLASS_DDL) == 0)
+			class = LOG_DDL;
+		else if (pg_strcasecmp(token, CLASS_FUNCTION) == 0)
+			class = LOG_FUNCTION;
+		else if (pg_strcasecmp(token, CLASS_MISC) == 0)
+			class = LOG_MISC;
+		else if (pg_strcasecmp(token, CLASS_PARAMETER) == 0)
+			class = LOG_PARAMETER;
+		else if (pg_strcasecmp(token, CLASS_READ) == 0)
+			class = LOG_READ;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(f);
+			pfree(rawval);
+			list_free(flags);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap. */
+		if (subtract)
+			*f &= ~class;
+		else
+			*f |= class;
+	}
+
+	pfree(rawval);
+	list_free(flags);
+
+	/*
+	 * Store the bitmap for assign_pgaudit_log.
+	 */
+	*extra = f;
+
+	return true;
+}
+
+/*
+ * Set pgaudit_log from extra (ignoring newval, which has already been converted
+ * to a bitmap above). Note that extra may not be set if the assignment is to be
+ * suppressed.
+ */
+static void
+assign_pgaudit_log(const char *newval, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(uint64 *)extra;
+}
+
+/*
+ * Define GUC variables and install hooks upon module load.
+ */
+void
+_PG_init(void)
+{
+	if (IsUnderPostmaster)
+		ereport(ERROR,
+			(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+			errmsg("pg_audit must be loaded via shared_preload_libraries")));
+
+	/*
+	 * pg_audit.role = "audit"
+	 *
+	 * This variable defines a role to be used for auditing.
+	 */
+	DefineCustomStringVariable("pg_audit.role",
+							   "Enable auditing for role",
+							   NULL,
+							   &auditRole,
+							   "",
+							   PGC_SUSET,
+							   GUC_NOT_IN_SAMPLE,
+							   NULL, NULL, NULL);
+
+	/*
+	 * pg_audit.log = "read, write, ddl"
+	 *
+	 * This variables controls what classes of commands are logged.
+	 */
+	DefineCustomStringVariable("pg_audit.log",
+							   "Enable auditing for classes of commands",
+							   NULL,
+							   &auditLog,
+							   "none",
+							   PGC_SUSET,
+							   GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+							   check_pgaudit_log,
+							   assign_pgaudit_log,
+							   NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chain.
+	 */
+	next_ExecutorStart_hook = ExecutorStart_hook;
+	ExecutorStart_hook = pgaudit_ExecutorStart_hook;
+
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pgaudit_ExecutorCheckPerms_hook;
+
+	next_ExecutorEnd_hook = ExecutorEnd_hook;
+	ExecutorEnd_hook = pgaudit_ExecutorEnd_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pgaudit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pgaudit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000..6730c68
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.control
@@ -0,0 +1,5 @@
+# pg_audit extension
+comment = 'provides auditing functionality'
+default_version = '1.0.0'
+module_pathname = '$libdir/pg_audit'
+relocatable = true
diff --git a/contrib/pg_audit/test/test.pl b/contrib/pg_audit/test/test.pl
new file mode 100755
index 0000000..f5cbde2
--- /dev/null
+++ b/contrib/pg_audit/test/test.pl
@@ -0,0 +1,1412 @@
+#!/usr/bin/perl
+################################################################################
+# test.pl - pg_audit Unit Tests
+################################################################################
+
+################################################################################
+# Perl includes
+################################################################################
+use strict;
+use warnings;
+use Carp;
+
+use Getopt::Long;
+use Pod::Usage;
+use DBI;
+use Cwd qw(abs_path);
+use IPC::System::Simple qw(capture);
+
+################################################################################
+# Constants
+################################################################################
+use constant
+{
+	true  => 1,
+	false => 0
+};
+
+use constant
+{
+	CONTEXT_GLOBAL   => 'GLOBAL',
+	CONTEXT_DATABASE => 'DATABASE',
+	CONTEXT_ROLE	 => 'ROLE'
+};
+
+use constant
+{
+	CLASS			=> 'CLASS',
+
+	CLASS_DDL		=> 'DDL',
+	CLASS_FUNCTION	=> 'FUNCTION',
+	CLASS_MISC		=> 'MISC',
+	CLASS_PARAMETER => 'PARAMETER',
+	CLASS_READ		=> 'READ',
+	CLASS_WRITE		=> 'WRITE',
+
+	CLASS_ALL		=> 'ALL',
+	CLASS_NONE		=> 'NONE'
+};
+
+use constant
+{
+	COMMAND						=> 'COMMAND',
+	COMMAND_LOG					=> 'COMMAND_LOG',
+
+	COMMAND_ANALYZE					=> 'ANALYZE',
+	COMMAND_ALTER_AGGREGATE			=> 'ALTER AGGREGATE',
+	COMMAND_ALTER_COLLATION			=> 'ALTER COLLATION',
+	COMMAND_ALTER_CONVERSION		=> 'ALTER CONVERSION',
+	COMMAND_ALTER_DATABASE			=> 'ALTER DATABASE',
+	COMMAND_ALTER_ROLE				=> 'ALTER ROLE',
+	COMMAND_ALTER_ROLE_SET			=> 'ALTER ROLE SET',
+	COMMAND_ALTER_TABLE				=> 'ALTER TABLE',
+	COMMAND_ALTER_TABLE_COLUMN		=> 'ALTER TABLE COLUMN',
+	COMMAND_ALTER_TABLE_INDEX		=> 'ALTER TABLE INDEX',
+	COMMAND_BEGIN					=> 'BEGIN',
+	COMMAND_CLOSE					=> 'CLOSE CURSOR',
+	COMMAND_COMMIT					=> 'COMMIT',
+	COMMAND_COPY					=> 'COPY',
+	COMMAND_COPY_TO					=> 'COPY TO',
+	COMMAND_COPY_FROM				=> 'COPY FROM',
+	COMMAND_CREATE_AGGREGATE		=> 'CREATE AGGREGATE',
+	COMMAND_CREATE_COLLATION		=> 'CREATE COLLATION',
+	COMMAND_CREATE_CONVERSION		=> 'CREATE CONVERSION',
+	COMMAND_CREATE_DATABASE			=> 'CREATE DATABASE',
+	COMMAND_CREATE_INDEX			=> 'CREATE INDEX',
+	COMMAND_DEALLOCATE				=> 'DEALLOCATE',
+	COMMAND_DECLARE_CURSOR			=> 'DECLARE CURSOR',
+	COMMAND_DO						=> 'DO',
+	COMMAND_DISCARD_ALL				=> 'DISCARD ALL',
+	COMMAND_CREATE_FUNCTION			=> 'CREATE FUNCTION',
+	COMMAND_CREATE_ROLE				=> 'CREATE ROLE',
+	COMMAND_CREATE_SCHEMA			=> 'CREATE SCHEMA',
+	COMMAND_CREATE_TABLE			=> 'CREATE TABLE',
+	COMMAND_CREATE_TABLE_AS			=> 'CREATE TABLE AS',
+	COMMAND_DROP_DATABASE			=> 'DROP DATABASE',
+	COMMAND_DROP_SCHEMA				=> 'DROP SCHEMA',
+	COMMAND_DROP_TABLE				=> 'DROP TABLE',
+	COMMAND_DROP_TABLE_CONSTRAINT	=> 'DROP TABLE CONSTRAINT',
+	COMMAND_DROP_TABLE_INDEX		=> 'DROP TABLE INDEX',
+	COMMAND_DROP_TABLE_TOAST		=> 'DROP TABLE TOAST',
+	COMMAND_DROP_TABLE_TYPE			=> 'DROP TABLE TYPE',
+	COMMAND_EXECUTE					=> 'EXECUTE',
+	COMMAND_EXECUTE_READ			=> 'EXECUTE READ',
+	COMMAND_EXECUTE_WRITE			=> 'EXECUTE WRITE',
+	COMMAND_EXECUTE_FUNCTION		=> 'EXECUTE FUNCTION',
+	COMMAND_EXPLAIN					=> 'EXPLAIN',
+	COMMAND_FETCH					=> 'FETCH',
+	COMMAND_GRANT					=> 'GRANT',
+	COMMAND_INSERT					=> 'INSERT',
+	# COMMAND_PARAMETER				=> 'PARAMETER',
+	# COMMAND_PARAMETER_READ			=> 'PARAMETER_READ',
+	# COMMAND_PARAMETER_WRITE			=> 'PARAMETER_WRITE',
+	COMMAND_PREPARE					=> 'PREPARE',
+	COMMAND_PREPARE_READ			=> 'PREPARE READ',
+	COMMAND_PREPARE_WRITE			=> 'PREPARE WRITE',
+	COMMAND_REVOKE					=> 'REVOKE',
+	COMMAND_SELECT					=> 'SELECT',
+	COMMAND_SET						=> 'SET',
+	COMMAND_UPDATE					=> 'UPDATE'
+};
+
+use constant
+{
+	TYPE					=> 'TYPE',
+	TYPE_NONE				=> '',
+
+	TYPE_AGGREGATE			=> 'AGGREGATE',
+	TYPE_COLLATION			=> 'COLLATION',
+	TYPE_CONVERSION			=> 'CONVERSION',
+	TYPE_SCHEMA				=> 'SCHEMA',
+	TYPE_FUNCTION			=> 'FUNCTION',
+	TYPE_INDEX				=> 'INDEX',
+	TYPE_TABLE				=> 'TABLE',
+	TYPE_TABLE_COLUMN		=> 'TABLE COLUMN',
+	TYPE_TABLE_CONSTRAINT	=> 'TABLE CONSTRAINT',
+	TYPE_TABLE_TOAST		=> 'TABLE TOAST',
+	TYPE_TYPE				=> 'TYPE'
+};
+
+use constant
+{
+	NAME			=> 'NAME'
+};
+
+################################################################################
+# Command line parameters
+################################################################################
+my $strPgSqlBin = '../../../../bin/bin';	# Path of PG binaries to use for
+											# this test
+my $strTestPath = '../../../../data';		# Path where testing will occur
+my $iDefaultPort = 6000;					# Default port to run Postgres on
+my $bHelp = false;							# Display help
+my $bQuiet = false;							# Supress output except for errors
+my $bNoCleanup = false;						# Cleanup database on exit
+
+GetOptions ('q|quiet' => \$bQuiet,
+			'no-cleanup' => \$bNoCleanup,
+			'help' => \$bHelp,
+			'pgsql-bin=s' => \$strPgSqlBin,
+			'test-path=s' => \$strTestPath)
+	or pod2usage(2);
+
+# Display version and exit if requested
+if ($bHelp)
+{
+	print 'pg_audit unit test\n\n';
+	pod2usage();
+
+	exit 0;
+}
+
+################################################################################
+# Global variables
+################################################################################
+my $hDb;					# Connection to Postgres
+my $strLogExpected = '';	# The expected log compared with grepping AUDIT
+							# entries from the postgres log.
+
+my $strDatabase = 'postgres';	# Connected database (modified by PgSetDatabase)
+my $strUser = 'postgres';		# Connected user (modified by PgSetUser)
+my $strAuditRole = 'audit';		# Role to use for auditing
+
+my %oAuditLogHash;				# Hash to store pg_audit.log GUCS
+my %oAuditGrantHash;			# Hash to store pg_audit grants
+
+my $strCurrentAuditLog;		# pg_audit.log setting was Postgres was started with
+my $strTemporaryAuditLog;	# pg_audit.log setting that was set hot
+
+################################################################################
+# Stores the mapping between commands, classes, and types
+################################################################################
+my %oCommandHash =
+(&COMMAND_ANALYZE => {
+	&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_AGGREGATE => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_AGGREGATE},
+	&COMMAND_ALTER_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_COLLATION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_COLLATION},
+	&COMMAND_ALTER_CONVERSION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_CONVERSION},
+	&COMMAND_ALTER_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_ALTER_ROLE_SET => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_ALTER_ROLE},
+	&COMMAND_ALTER_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_ALTER_TABLE_COLUMN => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_COLUMN, &COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_ALTER_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_ALTER_TABLE},
+	&COMMAND_BEGIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CLOSE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COMMIT => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_COPY_FROM => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_COPY_TO => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_COPY},
+	&COMMAND_CREATE_AGGREGATE => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_AGGREGATE},
+	&COMMAND_CREATE_CONVERSION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_CONVERSION},
+	&COMMAND_CREATE_COLLATION => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_COLLATION},
+	&COMMAND_CREATE_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX},
+	&COMMAND_DEALLOCATE => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_DECLARE_CURSOR => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_DO => {&CLASS => &CLASS_FUNCTION, &TYPE => &TYPE_NONE},
+	&COMMAND_DISCARD_ALL => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_FUNCTION => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_FUNCTION},
+	&COMMAND_CREATE_ROLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_CREATE_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_SCHEMA},
+	&COMMAND_CREATE_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_CREATE_TABLE_AS => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_DATABASE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_SCHEMA => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_NONE},
+	&COMMAND_DROP_TABLE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_DROP_TABLE_CONSTRAINT => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_CONSTRAINT, &COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_INDEX => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_INDEX,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TOAST => {&CLASS => &CLASS_DDL,
+		&TYPE => &TYPE_TABLE_TOAST, &COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_DROP_TABLE_TYPE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TYPE,
+		&COMMAND => &COMMAND_DROP_TABLE},
+	&COMMAND_EXECUTE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXECUTE_FUNCTION => {&CLASS => &CLASS_FUNCTION,
+		&TYPE => &TYPE_FUNCTION, &COMMAND => &COMMAND_EXECUTE},
+	&COMMAND_EXPLAIN => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_FETCH => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_GRANT => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_PREPARE_READ => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_PREPARE_WRITE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE,
+		&COMMAND => &COMMAND_PREPARE},
+	&COMMAND_INSERT => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE},
+	&COMMAND_REVOKE => {&CLASS => &CLASS_DDL, &TYPE => &TYPE_TABLE},
+	&COMMAND_SELECT => {&CLASS => &CLASS_READ, &TYPE => &TYPE_NONE},
+	&COMMAND_SET => {&CLASS => &CLASS_MISC, &TYPE => &TYPE_NONE},
+	&COMMAND_UPDATE => {&CLASS => &CLASS_WRITE, &TYPE => &TYPE_NONE}
+);
+
+################################################################################
+# CommandExecute
+################################################################################
+sub CommandExecute
+{
+	my $strCommand = shift;
+	my $bSuppressError = shift;
+
+	# Set default
+	$bSuppressError = defined($bSuppressError) ? $bSuppressError : false;
+
+	# Run the command
+	my $iResult = system($strCommand);
+
+	if ($iResult != 0 && !$bSuppressError)
+	{
+		confess "command '${strCommand}' failed with error ${iResult}";
+	}
+}
+
+################################################################################
+# log
+################################################################################
+sub log
+{
+	my $strMessage = shift;
+	my $bError = shift;
+
+	# Set default
+	$bError = defined($bError) ? $bError : false;
+
+	if (!$bQuiet)
+	{
+		print "${strMessage}\n";
+	}
+
+	if ($bError)
+	{
+		exit 1;
+	}
+}
+
+################################################################################
+# ArrayToString
+################################################################################
+sub ArrayToString
+{
+	my @stryArray = @_;
+
+	my $strResult = '';
+
+	for (my $iIndex = 0; $iIndex < @stryArray; $iIndex++)
+	{
+		if ($iIndex != 0)
+		{
+			$strResult .= ', ';
+		}
+
+		$strResult .= $stryArray[$iIndex];
+	}
+
+	return $strResult;
+}
+
+################################################################################
+# BuildModule
+################################################################################
+sub BuildModule
+{
+	capture('cd ..;make');
+	CommandExecute("cp ../pg_audit.so" .
+				   " ${strPgSqlBin}/../lib/postgresql");
+	CommandExecute("cp ../pg_audit.control" .
+				   " ${strPgSqlBin}/../share/postgresql/extension");
+	CommandExecute("cp ../pg_audit--1.0.0.sql" .
+				   " ${strPgSqlBin}/../share/postgresql/extension");
+}
+
+################################################################################
+# PgConnect
+################################################################################
+sub PgConnect
+{
+	my $iPort = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+
+	# Log Connection
+	&log("   DB: connect user ${strUser}, database ${strDatabase}");
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# Connect to the db
+	$hDb = DBI->connect("dbi:Pg:dbname=${strDatabase};port=${iPort};host=/tmp",
+						$strUser, undef,
+						{AutoCommit => 1, RaiseError => 1});
+}
+
+################################################################################
+# PgDisconnect
+################################################################################
+sub PgDisconnect
+{
+	# Connect to the db (whether it is local or remote)
+	if (defined($hDb))
+	{
+		$hDb->disconnect;
+		undef($hDb);
+	}
+}
+
+################################################################################
+# PgExecute
+################################################################################
+sub PgExecute
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	my $hStatement = $hDb->prepare($strSql);
+
+	$hStatement->execute();
+	$hStatement->finish();
+}
+
+################################################################################
+# PgExecuteOnly
+################################################################################
+sub PgExecuteOnly
+{
+	my $strSql = shift;
+
+	# Log the statement
+	&log("  SQL: ${strSql}");
+
+	# Execute the statement
+	$hDb->do($strSql);
+}
+
+################################################################################
+# PgSetDatabase
+################################################################################
+sub PgSetDatabase
+{
+	my $strDatabaseParam = shift;
+
+	# Stop and start the database to reset pgconf entries
+	PgStop();
+	PgStart();
+
+	# Execute the statement
+	$strDatabase = $strDatabaseParam;
+	PgConnect();
+}
+
+################################################################################
+# PgSetUser
+################################################################################
+sub PgSetUser
+{
+	my $strUserParam = shift;
+
+	$strUser = $strUserParam;
+
+	# Stop and start the database to reset pgconf entries
+	if ((defined($strTemporaryAuditLog) && !defined($strCurrentAuditLog)) ||
+		(defined($strCurrentAuditLog) && !defined($strTemporaryAuditLog)) ||
+		$strCurrentAuditLog ne $strTemporaryAuditLog)
+	{
+		$strCurrentAuditLog = $strTemporaryAuditLog;
+
+		PgStop();
+		PgStart();
+	}
+	else
+	{
+		# Execute the statement
+		PgConnect();
+	}
+}
+
+################################################################################
+# SaveString
+################################################################################
+sub SaveString
+{
+	my $strFile = shift;
+	my $strString = shift;
+
+	# Open the file for writing
+	my $hFile;
+
+	open($hFile, '>', $strFile)
+		or confess "unable to open ${strFile}";
+
+	if ($strString ne '')
+	{
+		syswrite($hFile, $strString)
+			or confess "unable to write to ${strFile}: $!";
+	}
+
+	close($hFile);
+}
+
+################################################################################
+# PgLogExecute
+################################################################################
+sub PgLogExecute
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $oData = shift;
+	my $bExecute = shift;
+	my $bWait = shift;
+	my $bLogSql = shift;
+	my $strParameter = shift;
+	my $bExpectError = shift;
+
+	# Set defaults
+	$bExecute = defined($bExecute) ? $bExecute : true;
+	$bWait = defined($bWait) ? $bWait : true;
+	$bLogSql = defined($bLogSql) ? $bLogSql : true;
+
+	if ($bExecute)
+	{
+		eval
+		{
+			PgExecuteOnly($strSql);
+		};
+
+		if ($@ && !$bExpectError)
+		{
+			confess $@;
+		}
+	}
+
+	PgLogExpect($strCommand, $bLogSql ? $strSql : '', $strParameter, $oData);
+
+	if ($bWait)
+	{
+		PgLogWait();
+	}
+}
+
+################################################################################
+# QuoteCSV
+################################################################################
+sub QuoteCSV
+{
+	my $strCSV = shift;
+
+	if (defined($strCSV) &&
+		(index($strCSV, ',') >= 0 || index($strCSV, '"') > 0 ||
+		 index($strCSV, "\n") > 0 || index($strCSV, "\r") >= 0))
+	{
+		$strCSV =~ s/"/""/g;
+		$strCSV = "\"${strCSV}\"";
+	}
+
+	return $strCSV;
+}
+
+################################################################################
+# PgLogExpect
+################################################################################
+sub PgLogExpect
+{
+	my $strCommand = shift;
+	my $strSql = shift;
+	my $strParameter = shift;
+	my $oData = shift;
+
+	# If oData is false then no logging
+	if (defined($oData) && ref($oData) eq '' && !$oData)
+	{
+		return;
+	}
+
+	# Quote SQL if needs to be quoted
+	$strSql = QuoteCSV($strSql);
+
+	if (defined($strParameter))
+	{
+		$strSql .= ",${strParameter}";
+	}
+
+	# Log based on session
+	if (PgShouldLog($strCommand))
+	{
+		# Make sure class is defined
+		my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+		if (!defined($strClass))
+		{
+			confess "class is not defined for command ${strCommand}";
+		}
+
+		# Make sure object type is defined
+		my $strObjectType = $oCommandHash{$strCommand}{&TYPE};
+
+		if (!defined($strObjectType))
+		{
+			confess "object type is not defined for command ${strCommand}";
+		}
+
+		# Check for command override
+		my $strCommandLog = $strCommand;
+
+		if ($oCommandHash{$strCommand}{&COMMAND})
+		{
+			$strCommandLog = $oCommandHash{$strCommand}{&COMMAND};
+		}
+
+		my $strObjectName = '';
+
+		if (defined($oData) && ref($oData) ne 'ARRAY')
+		{
+			$strObjectName = QuoteCSV($oData);
+		}
+
+		my $strLog .= "SESSION,${strClass},${strCommandLog}," .
+					  "${strObjectType},${strObjectName},${strSql}";
+		&log("AUDIT: ${strLog}");
+
+		$strLogExpected .= "${strLog}\n";
+	}
+
+	# Log based on grants
+	if (ref($oData) eq 'ARRAY' && ($strCommand eq COMMAND_SELECT ||
+		$oCommandHash{$strCommand}{&CLASS} eq CLASS_WRITE))
+	{
+		foreach my $oTableHash (@{$oData})
+		{
+			my $strObjectName = QuoteCSV(${$oTableHash}{&NAME});
+			my $strCommandLog = ${$oTableHash}{&COMMAND};
+
+			if (defined($oAuditGrantHash{$strAuditRole}
+										{$strObjectName}{$strCommandLog}))
+			{
+				my $strCommandLog = defined(${$oTableHash}{&COMMAND_LOG}) ?
+					${$oTableHash}{&COMMAND_LOG} : $strCommandLog;
+				my $strClass = $oCommandHash{$strCommandLog}{&CLASS};
+				my $strObjectType = ${$oTableHash}{&TYPE};
+
+				my $strLog .= "OBJECT,${strClass},${strCommandLog}," .
+							  "${strObjectType},${strObjectName},${strSql}";
+				&log("AUDIT: ${strLog}");
+
+				$strLogExpected .= "${strLog}\n";
+			}
+		}
+
+		$oData = undef;
+	}
+}
+
+################################################################################
+# PgShouldLog
+################################################################################
+sub PgShouldLog
+{
+	my $strCommand = shift;
+
+	# Make sure class is defined
+	my $strClass = $oCommandHash{$strCommand}{&CLASS};
+
+	if (!defined($strClass))
+	{
+		confess "class is not defined for command ${strCommand}";
+	}
+
+	# Check logging for the role
+	my $bLog = undef;
+
+	if (defined($oAuditLogHash{&CONTEXT_ROLE}{$strUser}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_ROLE}{$strUser}{$strClass};
+	}
+
+	# Else check logging for the db
+	elsif (defined($oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_DATABASE}{$strDatabase}{$strClass};
+	}
+
+	# Else check logging for global
+	elsif (defined($oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}))
+	{
+		$bLog = $oAuditLogHash{&CONTEXT_GLOBAL}{&CONTEXT_GLOBAL}{$strClass};
+	}
+
+	return defined($bLog) ? true : false;
+}
+
+################################################################################
+# PgLogWait
+################################################################################
+sub PgLogWait
+{
+	my $strLogActual;
+
+	# Run in an eval block since grep returns 1 when nothing was found
+	eval
+	{
+		$strLogActual = capture("grep 'LOG:  AUDIT: '" .
+								" ${strTestPath}/postgresql.log");
+	};
+
+	# If an error was returned, continue if it was 1, otherwise confess
+	if ($@)
+	{
+		my $iExitStatus = $? >> 8;
+
+		if ($iExitStatus != 1)
+		{
+			confess "grep returned ${iExitStatus}";
+		}
+
+		$strLogActual = '';
+	}
+
+	# Strip the AUDIT and timestamp from the actual log
+	$strLogActual =~ s/prefix LOG:  AUDIT\: //g;
+	$strLogActual =~ s/SESSION,[0-9]+,[0-9]+,/SESSION,/g;
+	$strLogActual =~ s/OBJECT,[0-9]+,[0-9]+,/OBJECT,/g;
+
+	# Save the logs
+	SaveString("${strTestPath}/audit.actual", $strLogActual);
+	SaveString("${strTestPath}/audit.expected", $strLogExpected);
+
+	CommandExecute("diff ${strTestPath}/audit.expected" .
+				   " ${strTestPath}/audit.actual");
+}
+
+################################################################################
+# PgDrop
+################################################################################
+sub PgDrop
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Stop the cluster
+	PgStop(true, $strPath);
+
+	# Remove the directory
+	CommandExecute("rm -rf ${strTestPath}");
+}
+
+################################################################################
+# PgCreate
+################################################################################
+sub PgCreate
+{
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	CommandExecute("${strPgSqlBin}/initdb -D ${strPath} -U ${strUser}" .
+				   ' -A trust > /dev/null');
+}
+
+################################################################################
+# PgStop
+################################################################################
+sub PgStop
+{
+	my $bImmediate = shift;
+	my $strPath = shift;
+
+	# Set default
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+	$bImmediate = defined($bImmediate) ? $bImmediate : false;
+
+	# Disconnect user session
+	PgDisconnect();
+
+	# If postmaster process is running then stop the cluster
+	if (-e $strPath . '/postmaster.pid')
+	{
+		CommandExecute("${strPgSqlBin}/pg_ctl stop -D ${strPath} -w -s -m " .
+					  ($bImmediate ? 'immediate' : 'fast'));
+	}
+}
+
+################################################################################
+# PgStart
+################################################################################
+sub PgStart
+{
+	my $iPort = shift;
+	my $strPath = shift;
+
+	# Set default
+	$iPort = defined($iPort) ? $iPort : $iDefaultPort;
+	$strPath = defined($strPath) ? $strPath : $strTestPath;
+
+	# Make sure postgres is not running
+	if (-e $strPath . '/postmaster.pid')
+	{
+		confess "${strPath}/postmaster.pid exists, cannot start";
+	}
+
+	# Start the cluster
+	CommandExecute("${strPgSqlBin}/pg_ctl start -o \"" .
+				   "-c port=${iPort}" .
+				   " -c unix_socket_directories='/tmp'" .
+				   " -c shared_preload_libraries='pg_audit'" .
+				   " -c log_min_messages=debug1" .
+				   " -c log_line_prefix='prefix '" .
+				   " -c log_statement=all" .
+				   (defined($strCurrentAuditLog) ?
+					   " -c pg_audit.log='${strCurrentAuditLog}'" : '') .
+				   " -c pg_audit.role='${strAuditRole}'" .
+				   " -c log_connections=on" .
+				   "\" -D ${strPath} -l ${strPath}/postgresql.log -w -s");
+
+	# Connect user session
+	PgConnect();
+}
+
+################################################################################
+# PgAuditLogSet
+################################################################################
+sub PgAuditLogSet
+{
+	my $strContext = shift;
+	my $strName = shift;
+	my @stryClass = @_;
+
+	# Create SQL to set the GUC
+	my $strCommand;
+	my $strSql;
+
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		$strCommand = COMMAND_SET;
+		$strSql = "set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+		$strTemporaryAuditLog = ArrayToString(@stryClass);
+	}
+	elsif ($strContext eq CONTEXT_ROLE)
+	{
+		$strCommand = COMMAND_ALTER_ROLE_SET;
+		$strSql = "alter role ${strName} set pg_audit.log = '" .
+				  ArrayToString(@stryClass) . "'";
+	}
+	else
+	{
+		confess "unable to set pg_audit.log for context ${strContext}";
+	}
+
+	# Reset the audit log
+	if ($strContext eq CONTEXT_GLOBAL)
+	{
+		delete($oAuditLogHash{$strContext});
+		$strName = CONTEXT_GLOBAL;
+	}
+	else
+	{
+		delete($oAuditLogHash{$strContext}{$strName});
+	}
+
+	# Store all the classes in the hash and build the GUC
+	foreach my $strClass (@stryClass)
+	{
+		if ($strClass eq CLASS_ALL)
+		{
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_DDL} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_FUNCTION} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_MISC} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_READ} = true;
+			$oAuditLogHash{$strContext}{$strName}{&CLASS_WRITE} = true;
+		}
+
+		if (index($strClass, '-') == 0)
+		{
+			$strClass = substr($strClass, 1);
+
+			delete($oAuditLogHash{$strContext}{$strName}{$strClass});
+		}
+		else
+		{
+			$oAuditLogHash{$strContext}{$strName}{$strClass} = true;
+		}
+	}
+
+	PgLogExecute($strCommand, $strSql);
+}
+
+################################################################################
+# PgAuditGrantSet
+################################################################################
+sub PgAuditGrantSet
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_GRANT, "GRANT " .
+								(defined($strColumn) ?
+									lc(${strPrivilege}) ." (${strColumn})" :
+									uc(${strPrivilege})) .
+								" ON TABLE ${strObject} TO ${strRole} ");
+
+	$oAuditGrantHash{$strRole}{$strObject}{$strPrivilege} = true;
+}
+
+################################################################################
+# PgAuditGrantReset
+################################################################################
+sub PgAuditGrantReset
+{
+	my $strRole = shift;
+	my $strPrivilege = shift;
+	my $strObject = shift;
+	my $strColumn = shift;
+
+	# Create SQL to set the grant
+	PgLogExecute(COMMAND_REVOKE, "REVOKE  " . uc(${strPrivilege}) .
+				 (defined($strColumn) ? " (${strColumn})" : '') .
+				 " ON TABLE ${strObject} FROM ${strRole} ");
+
+	delete($oAuditGrantHash{$strRole}{$strObject}{$strPrivilege});
+}
+
+################################################################################
+# Main
+################################################################################
+my @oyTable;	   # Store table info for select, insert, update, delete
+my $strSql;		# Hold Sql commands
+
+# Drop the old cluster, build the code, and create a new cluster
+PgDrop();
+BuildModule();
+PgCreate();
+PgStart();
+
+PgExecute("create extension pg_audit");
+
+# Create test users and the audit role
+PgExecute("create user user1");
+PgExecute("create user user2");
+PgExecute("create role ${strAuditRole}");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL));
+
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_READ, CLASS_WRITE));
+
+# User1 follows the global log settings
+PgSetUser('user1');
+
+$strSql = 'CREATE  TABLE  public.test (id pg_catalog.int4   )' .
+		  '  WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test');
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+$strSql = 'drop table test';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'public.test');
+
+PgSetUser('user2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test2 (id int)', 'public.test2');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test2');
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test3 (id int)', 'public.test2');
+
+# Catalog select should not log
+PgLogExecute(COMMAND_SELECT, 'select * from pg_class limit 1',
+							   false);
+
+# Multi-table select
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test3, test2',
+							   \@oyTable);
+
+# Various CTE combinations
+PgAuditGrantSet($strAuditRole, &COMMAND_INSERT, 'public.test3');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (select id from test2)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (insert into test3 values (1) returning id)' .
+			 ' insert into test2 select id from cte',
+			 \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE, 'public.test2');
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_INSERT,
+			 'with cte as (update test2 set id = 1 returning id)' .
+			 ' insert into test3 select id from cte',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.test3', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT},
+			{&NAME => 'public.test2', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_INSERT});
+PgLogExecute(COMMAND_UPDATE,
+			 'with cte as (insert into test2 values (1) returning id)' .
+			 ' update test3 set id = cte.id' .
+			 ' from cte where test3.id <> cte.id',
+			 \@oyTable);
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user2', (CLASS_NONE));
+PgSetUser('user2');
+
+# Column-based audits
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table test4 (id int, name text)', 'public.test4');
+PgAuditGrantSet($strAuditRole, COMMAND_SELECT, 'public.test4', 'name');
+PgAuditGrantSet($strAuditRole, COMMAND_UPDATE, 'public.test4', 'id');
+PgAuditGrantSet($strAuditRole, COMMAND_INSERT, 'public.test4', 'name');
+
+# Select
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id from public.test4',
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from public.test4',
+							  \@oyTable);
+
+# Insert
+@oyTable = ();
+PgLogExecute(COMMAND_INSERT, 'insert into public.test4 (id) values (1)',
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_INSERT});
+PgLogExecute(COMMAND_INSERT, "insert into public.test4 (name) values ('test')",
+							  \@oyTable);
+
+# Update
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set name = 'foo'",
+							   \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update public.test4 set id = 1",
+							  \@oyTable);
+
+@oyTable = ({&NAME => 'public.test4', &TYPE => &TYPE_TABLE,
+			&COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update public.test4 set name = 'foo' where name = 'bar'",
+			 \@oyTable);
+
+# Drop test tables
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test2", 'public.test2');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test3", 'public.test3');
+PgLogExecute(COMMAND_DROP_TABLE, "drop table test4", 'public.test4');
+
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Create some email friendly tests.  These first tests are session logging only.
+PgSetUser('postgres');
+
+&log("\nExamples:");
+
+&log("\nSession Audit:\n");
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_DDL, CLASS_READ));
+
+PgSetUser('user1');
+
+$strSql = 'CREATE  TABLE  public.account (id pg_catalog.int4   ,' .
+		  ' name pg_catalog.text   COLLATE pg_catalog."default", ' .
+		  'password pg_catalog.text   COLLATE pg_catalog."default", '.
+		  'description pg_catalog.text   COLLATE pg_catalog."default")  '.
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.account');
+PgLogExecute(COMMAND_SELECT,
+			 'select * from account');
+PgLogExecute(COMMAND_INSERT,
+			 "insert into account (id, name, password, description)" .
+			 " values (1, 'user1', 'HASH1', 'blah, blah')");
+&log("AUDIT: <nothing logged>");
+
+# Now tests for object logging
+&log("\nObject Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_NONE));
+PgExecute("set pg_audit.role = 'audit'");
+PgSetUser('user1');
+
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account', 'password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_SELECT, 'select id, name from account',
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+PgAuditGrantSet($strAuditRole, &COMMAND_UPDATE,
+				'public.account', 'name, password');
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Now tests for session/object logging
+&log("\nSession/Object Audit:\n");
+
+PgSetUser('postgres');
+PgAuditLogSet(CONTEXT_ROLE, 'user1', (CLASS_READ, CLASS_WRITE));
+PgSetUser('user1');
+
+PgLogExecute(COMMAND_CREATE_TABLE,
+			 'create table account_role_map (account_id int, role_id int)',
+			 'public.account_role_map');
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.account_role_map');
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT},
+			{&NAME => 'public.account_role_map', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT,
+			 'select account.password, account_role_map.role_id from account' .
+			 ' inner join account_role_map' .
+			 ' on account.id = account_role_map.account_id',
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select password from account',
+							  \@oyTable);
+
+@oyTable = ();
+PgLogExecute(COMMAND_UPDATE, "update account set description = 'yada, yada'",
+							  \@oyTable);
+&log("AUDIT: <nothing logged>");
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT, &COMMAND_LOG => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE,
+			 "update account set description = 'yada, yada'" .
+			 " where password = 'HASH2'",
+			 \@oyTable);
+
+@oyTable = ({&NAME => 'public.account', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_UPDATE});
+PgLogExecute(COMMAND_UPDATE, "update account set password = 'HASH2'",
+							  \@oyTable);
+
+# Test all sql commands
+&log("\nExhaustive Command Tests:\n");
+
+PgSetUser('postgres');
+
+PgAuditLogSet(CONTEXT_GLOBAL, undef, (CLASS_ALL));
+PgLogExecute(COMMAND_SET, "set pg_audit.role = 'audit'");
+
+PgLogExecute(COMMAND_DO, "do \$\$\ begin raise notice 'test'; end; \$\$;");
+
+$strSql = 'CREATE SCHEMA  test ';
+PgLogExecute(COMMAND_CREATE_SCHEMA, $strSql, 'test');
+
+# Test COPY
+PgLogExecute(COMMAND_COPY_TO,
+			 "COPY pg_class to '" . abs_path($strTestPath) . "/class.out'");
+
+$strSql = 'CREATE  TABLE  test.pg_class  WITH (oids=OFF)   AS SELECT relname,' .
+		  ' relnamespace, reltype, reloftype, relowner, relam, relfilenode, ' .
+		  'reltablespace, relpages, reltuples, relallvisible, reltoastrelid, ' .
+		  'relhasindex, relisshared, relpersistence, relkind, relnatts, ' .
+		  'relchecks, relhasoids, relhaspkey, relhasrules, relhastriggers, ' .
+		  'relhassubclass, relrowsecurity, relispopulated, relreplident, ' .
+		  'relfrozenxid, relminmxid, relacl, reloptions ' .
+		  'FROM pg_catalog.pg_class ';
+PgLogExecute(COMMAND_INSERT, $strSql, undef, true, false);
+PgLogExecute(COMMAND_CREATE_TABLE_AS, $strSql, 'test.pg_class', false, true);
+
+$strSql = "COPY test.pg_class from '" . abs_path($strTestPath) . "/class.out'";
+PgLogExecute(COMMAND_INSERT, $strSql);
+#PgLogExecute(COMMAND_COPY_FROM, $strSql, undef, false, true);
+
+# Test prepared SELECT
+PgLogExecute(COMMAND_PREPARE_READ,
+			 'PREPARE pgclassstmt (oid) as select *' .
+			 ' from pg_class where oid = $1');
+PgLogExecute(COMMAND_EXECUTE_READ,
+			 'EXECUTE pgclassstmt (1)');
+PgLogExecute(COMMAND_DEALLOCATE,
+			 'DEALLOCATE pgclassstmt');
+
+# Test cursor
+PgLogExecute(COMMAND_BEGIN,
+			 'BEGIN');
+PgLogExecute(COMMAND_DECLARE_CURSOR,
+			 'DECLARE ctest SCROLL CURSOR FOR SELECT * FROM pg_class');
+PgLogExecute(COMMAND_FETCH,
+			 'FETCH NEXT FROM ctest');
+PgLogExecute(COMMAND_CLOSE,
+			 'CLOSE ctest');
+PgLogExecute(COMMAND_COMMIT,
+			 'COMMIT');
+
+# Test prepared INSERT
+$strSql = 'CREATE  TABLE  test.test_insert (id pg_catalog.int4   )  ' .
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'test.test_insert');
+
+$strSql = 'PREPARE pgclassstmt (oid) as insert into test.test_insert (id) ' .
+		  'values ($1)';
+PgLogExecute(COMMAND_PREPARE_WRITE, $strSql);
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, "1");
+
+$strSql = 'EXECUTE pgclassstmt (1)';
+PgLogExecute(COMMAND_EXECUTE_WRITE, $strSql, undef, true, true);
+
+# Create a table with a primary key
+$strSql = 'CREATE  TABLE  public.test (id pg_catalog.int4   , ' .
+		  'name pg_catalog.text   COLLATE pg_catalog."default", description ' .
+		  'pg_catalog.text   COLLATE pg_catalog."default", CONSTRAINT ' .
+		  'test_pkey PRIMARY KEY (id))  WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_INDEX, $strSql, 'public.test_pkey', true, false);
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test', false, true);
+
+PgLogExecute(COMMAND_ANALYZE, 'analyze test');
+
+# Grant select to public - this should have no affect on auditing
+$strSql = 'GRANT SELECT ON TABLE public.test TO PUBLIC ';
+PgLogExecute(COMMAND_GRANT, $strSql);
+
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+
+# Now grant select to audit and it should be logged
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test');
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select * from test', \@oyTable);
+
+# Check columns granted to public and make sure they do not log
+PgAuditGrantReset($strAuditRole, &COMMAND_SELECT, 'public.test');
+
+$strSql = 'GRANT select (name) ON TABLE public.test TO PUBLIC ';
+PgLogExecute(COMMAND_GRANT, $strSql);
+
+PgLogExecute(COMMAND_SELECT, 'select * from test');
+PgLogExecute(COMMAND_SELECT, 'select from test');
+
+# Try a select that does not reference any tables
+PgLogExecute(COMMAND_SELECT, 'select 1, current_timestamp');
+
+# Now try the same in a do block
+$strSql = 'do $$ declare test int; begin select 1 into test; end $$';
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'select 1';
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, true);
+
+# Insert some data into test and try a loop in a do block
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (1)');
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (2)');
+PgLogExecute(COMMAND_INSERT, 'insert into test (id) values (3)');
+
+$strSql = 'do $$ ' .
+		  'declare ' .
+		  '	result record;' .
+		  'begin ' .
+		  '	for result in select id from test loop ' .
+		  '		insert into test (id) values (result.id + 100); ' .
+		  '	end loop; ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'select id from test';
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false);
+
+$strSql = 'insert into test (id) values (result.id + 100)';
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+PgLogExecute(COMMAND_INSERT, $strSql, undef, false, false, undef, ",,");
+
+# Test EXECUTE with bind
+$strSql = "select * from test where id = ?";
+my $hStatement = $hDb->prepare($strSql);
+
+$strSql = "select * from test where id = \$1";
+$hStatement->bind_param(1, 101);
+$hStatement->execute();
+
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false, undef, "101");
+
+$hStatement->bind_param(1, 103);
+$hStatement->execute();
+
+PgLogExecute(COMMAND_SELECT, $strSql, undef, false, false, undef, "103");
+
+$hStatement->finish();
+
+# Now try some DDL in a do block
+$strSql = 'do $$ ' .
+		  'begin ' .
+		  '	create table test_block (id int); ' .
+		  '	drop table test_block; ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, true, false);
+
+$strSql = 'CREATE  TABLE  public.test_block (id pg_catalog.int4   )  ' .
+		  'WITH (oids=OFF)  ';
+PgLogExecute(COMMAND_CREATE_TABLE, $strSql, 'public.test_block', false, false);
+
+$strSql = 'drop table test_block';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'public.test_block', false, false);
+
+# Generate an error in a do block and make sure the stack gets cleaned up
+$strSql = 'do $$ ' .
+		  'begin ' .
+		  '	create table bobus.test_block (id int); ' .
+		  'end; $$';
+
+PgLogExecute(COMMAND_DO, $strSql, undef, undef, undef, undef, undef, true);
+# PgLogExecute(COMMAND_SELECT, 'select 1');
+# exit 0;
+
+# Try explain
+PgLogExecute(COMMAND_SELECT, 'explain select 1', undef, true, false);
+PgLogExecute(COMMAND_EXPLAIN, 'explain select 1', undef, false, true);
+
+# Now set grant to a specific column to audit and make sure it logs
+# Make sure the the converse is true
+PgAuditGrantSet($strAuditRole, &COMMAND_SELECT, 'public.test',
+				'name, description');
+PgLogExecute(COMMAND_SELECT, 'select id from test');
+
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select name from test', \@oyTable);
+
+# Test alter and drop table statements
+$strSql = 'ALTER TABLE public.test DROP COLUMN description ';
+PgLogExecute(COMMAND_ALTER_TABLE_COLUMN,
+			 $strSql, 'public.test.description', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE,
+			 $strSql, 'public.test', false, true);
+@oyTable = ({&NAME => 'public.test', &TYPE => &TYPE_TABLE,
+			 &COMMAND => &COMMAND_SELECT});
+PgLogExecute(COMMAND_SELECT, 'select from test', \@oyTable);
+
+$strSql = 'ALTER TABLE  public.test RENAME TO test2';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'public.test2');
+
+$strSql = 'ALTER TABLE public.test2 SET SCHEMA test';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'test.test2');
+
+$strSql = 'ALTER TABLE test.test2 ADD COLUMN description pg_catalog.text   ' .
+		  'COLLATE pg_catalog."default"';
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql, 'test.test2');
+
+$strSql = 'ALTER TABLE test.test2 DROP COLUMN description ';
+PgLogExecute(COMMAND_ALTER_TABLE_COLUMN, $strSql,
+			 'test.test2.description', true, false);
+PgLogExecute(COMMAND_ALTER_TABLE, $strSql,
+			 'test.test2', false, true);
+
+$strSql = 'drop table test.test2';
+PgLogExecute(COMMAND_DROP_TABLE, $strSql, 'test.test2', true, false);
+PgLogExecute(COMMAND_DROP_TABLE_CONSTRAINT, $strSql, 'test_pkey on test.test2',
+			 false, false);
+PgLogExecute(COMMAND_DROP_TABLE_INDEX, $strSql, 'test.test_pkey', false, true);
+
+$strSql = "CREATE  FUNCTION public.int_add(IN a pg_catalog.int4 , IN b " .
+		  "pg_catalog.int4 ) RETURNS  pg_catalog.int4 LANGUAGE plpgsql  " .
+		  "VOLATILE  CALLED ON NULL INPUT SECURITY INVOKER COST 100   AS '" .
+		  " begin return a + b; end '";
+PgLogExecute(COMMAND_CREATE_FUNCTION, $strSql,
+			 'public.int_add(integer,integer)');
+PgLogExecute(COMMAND_SELECT, "select int_add(1, 1)",
+							 undef, true, false);
+PgLogExecute(COMMAND_EXECUTE_FUNCTION, "select int_add(1, 1)",
+									   'public.int_add', false, true);
+
+$strSql = "CREATE AGGREGATE public.sum_test(  pg_catalog.int4) " .
+		  "(SFUNC=public.int_add, STYPE=pg_catalog.int4, INITCOND='0')";
+PgLogExecute(COMMAND_CREATE_AGGREGATE, $strSql, 'public.sum_test(integer)');
+
+# There's a bug here in deparse:
+$strSql = "ALTER AGGREGATE public.sum_test(integer) RENAME TO sum_test2";
+PgLogExecute(COMMAND_ALTER_AGGREGATE, $strSql, 'public.sum_test2(integer)');
+
+$strSql = "CREATE COLLATION public.collation_test (LC_COLLATE = 'de_DE', " .
+		  "LC_CTYPE = 'de_DE')";
+PgLogExecute(COMMAND_CREATE_COLLATION, $strSql, 'public.collation_test');
+
+$strSql =  "ALTER COLLATION public.collation_test RENAME TO collation_test2";
+PgLogExecute(COMMAND_ALTER_COLLATION, $strSql, 'public.collation_test2');
+
+$strSql = "CREATE  CONVERSION public.conversion_test FOR 'SQL_ASCII' " .
+		  "TO 'MULE_INTERNAL' FROM pg_catalog.ascii_to_mic";
+PgLogExecute(COMMAND_CREATE_CONVERSION, $strSql, 'public.conversion_test');
+
+$strSql = "ALTER CONVERSION public.conversion_test RENAME TO conversion_test2";
+PgLogExecute(COMMAND_ALTER_CONVERSION, $strSql, 'public.conversion_test2');
+
+PgLogExecute(COMMAND_CREATE_DATABASE, "CREATE DATABASE database_test");
+PgLogExecute(COMMAND_ALTER_DATABASE,
+			 "ALTER DATABASE database_test rename to database_test2");
+PgLogExecute(COMMAND_DROP_DATABASE, "DROP DATABASE database_test2");
+
+# Make sure there are no more audit events pending in the postgres log
+PgLogWait();
+
+# Stop the database
+if (!$bNoCleanup)
+{
+	PgDrop();
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..5b247a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -124,6 +124,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &ltree;
  &pageinspect;
  &passwordcheck;
+ &pgaudit;
  &pgbuffercache;
  &pgcrypto;
  &pgfreespacemap;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 89fff77..6b0b407 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -125,6 +125,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbench         SYSTEM "pgbench.sgml">
 <!ENTITY pgarchivecleanup SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000..f9152cd
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,335 @@
+<!-- doc/src/sgml/pgaudit.sgml -->
+
+<sect1 id="pgaudit" xreflabel="pgaudit">
+  <title>pg_audit</title>
+
+  <indexterm zone="pgaudit">
+    <primary>pg_audit</primary>
+  </indexterm>
+
+  <para>
+    The <filename>pg_audit</filename> module provides session and object
+    auditing via the standard logging facility.  Session and object auditing are
+    completely independent and can be combined.
+  </para>
+
+  <sect2>
+    <title>Session Auditing</title>
+
+    <para>
+      Session auditing allows the logging of all commands that are executed by
+      a user in the backend.  Each command is logged with a single entry and
+      includes the audit type (e.g. <literal>SESSION</literal>), command type
+      (e.g. <literal>CREATE TABLE</literal>, <literal>SELECT</literal>) and
+      statement (e.g. <literal>"select * from test"</literal>).
+
+      Fully-qualified names and object types will be logged for
+      <literal>CREATE</literal>, <literal>UPDATE</literal>, and
+      <literal>DROP</literal> commands on <literal>TABLE</literal>,
+      <literal>MATVIEW</literal>, <literal>VIEW</literal>,
+      <literal>INDEX</literal>, <literal>FOREIGN TABLE</literal>,
+      <literal>COMPOSITE TYPE</literal>, <literal>INDEX</literal>, and
+      <literal>SEQUENCE</literal> objects as well as function calls.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is controlled by the <literal>pg_audit.log</literal>
+        GUC. There are six classes of commands that are recognized:
+
+        <itemizedlist>
+          <listitem>
+            <para>
+              <literal>READ</literal> - <literal>SELECT</literal> and
+              <literal>COPY</literal> when the source is a table or query.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>WRITE</literal> - <literal>INSERT</literal>,
+              <literal>UPDATE</literal>, <literal>DELETE</literal>,
+              <literal>TRUNCATE</literal>, and <literal>COPY</literal> when the
+              destination is a table.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>FUNCTION</literal> - Function calls and
+              <literal>DO</literal> blocks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>DDL</literal> - DDL, plus <literal>VACUUM</literal>,
+              <literal>REINDEX</literal>, and <literal>ANALYZE</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>PARAMETER</literal> - Parameters that were passed for the statement.  Parameters immediately follow the statement text. 
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>MISC</literal> - Miscellaneous commands, e.g.
+              <literal>DISCARD</literal>, <literal>FETCH</literal>,
+              <literal>CHECKPOINT</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Enable session logging for all writes and DDL:
+          <programlisting>
+pg_audit.log = 'write, ddl'
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except miscellaneous:
+          <programlisting>
+pg_audit.log = 'all, -misc'
+          </programlisting>
+      </para>
+      
+      <para>
+      Note that <literal>pg_audit.log</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.log = 'read, ddl'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+      <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+select *
+    from account;
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Object Auditing</title>
+
+    <para>
+      Object auditing logs commands that affect a particular object.  Only
+      <literal>SELECT</literal>, <literal>INSERT</literal>,
+      <literal>UPDATE</literal> and <literal>DELETE</literal> commands are
+      supported.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level auditing is implemented via the roles system.  The
+        <literal>pg_audit.role</literal> GUC defines the role that will be used
+        for auditing.  An object will be audited when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.
+      </para>
+
+      <programlisting>
+postresql.conf: pg_audit.role = 'audit'
+
+grant select, delete
+   on public.account;
+      </programlisting>
+
+      <para>
+      Note that <literal>pg_audit.role</literal> can be set globally (in 
+      <filename>postgresql.conf</filename>), at the database level (using
+      <literal>alter database ... set</literal>), or at the role level (using
+      <literal>alter role ... set</literal>).
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Examples</title>
+
+      <para>
+        Set <literal>pg_audit.role = 'audit'</literal> in
+        <literal>postgresql.conf</literal>.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to audit;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to audit;
+
+update account
+   set description = 'yada, yada';
+
+update account
+   set password = 'HASH2';
+
+create table account_role_map
+(
+    account_id int,
+    role_id int
+);
+
+grant select
+   on public.account_role_map
+   to audit;
+
+select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+        </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+AUDIT: OBJECT,READ,SELECT,TABLE,public.account_role_map,select account.password,
+       account_role_map.role_id
+  from account
+       inner join account_role_map
+            on account.id = account_role_map.account_id
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2>
+    <title>Format</title>
+
+    <para>
+      Audit entries are written to the standard logging facility and contain
+      the following columns in comma-separated format:
+
+      <note>
+        <para>
+          Output is not in compliant CSV format.  If machine-readability is
+          required then consider setting
+          <literal>log_destination = 'csvlog'</literal>.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</literal> - <literal>SESSION</literal> or
+            <literal>OBJECT</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</literal> - <literal>READ</literal>,
+            <literal>WRITE</literal>, <literal>FUNCTION</literal>,
+            <literal>DDL</literal>, or <literal>MISC</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</literal> - <literal>ALTER TABLE</literal>,
+            <literal>SELECT</literal>, <literal>CREATE INDEX</literal>,
+            <literal>UPDATE</literal>, etc.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</literal> - <literal>TABLE</literal>,
+            <literal>INDEX</literal>, <literal>VIEW</literal>, etc.  Only
+            available for DML and certain DDL commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</literal> - The fully-qualified object name
+            (e.g. public.account).  Only available for DML and certain DDL
+            commands.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</literal> - Statement execute on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Authors</title>
+
+    <para>
+      Abhijit Menon-Sen <email>[email protected]</email>, Ian Barwick <email>[email protected]</email>, and David Steele <email>[email protected]</email>.
+    </para>
+  </sect2>
+</sect1>


  [application/pgp-signature] signature.asc (819B, ../../[email protected]/3-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-24 14:28  Sawada Masahiko <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: Sawada Masahiko @ 2015-03-24 14:28 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: David Steele <[email protected]>; Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On Tue, Mar 24, 2015 at 3:17 AM, Alvaro Herrera
<[email protected]> wrote:
> Sawada Masahiko wrote:
>
>> I tied to look into latest patch, but got following error.
>>
>> masahiko [pg_audit] $ LANG=C make
>> gcc -Wall -Wmissing-prototypes -Wpointer-arith
>> -Wdeclaration-after-statement -Wendif-labels
>> -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
>> -fwrapv -g -fpic -I. -I. -I../../src/include -D_GNU_SOURCE   -c -o
>> pg_audit.o pg_audit.c
>> pg_audit.c: In function 'log_audit_event':
>> pg_audit.c:456: warning: ISO C90 forbids mixed declarations and code
>> pg_audit.c: In function 'pg_audit_ddl_command_end':
>> pg_audit.c:1436: error: 'pg_event_trigger_expand_command' undeclared
>> (first use in this function)
>
> You need to apply my deparsing patch first, last version of which I
> posted here:
> https://www.postgresql.org/message-id/[email protected]
>

Thank you for the info.
I've applied these patchese successfully.

I looked into this module, and had a few comments as follows.
1. pg_audit audits only one role currently.
In currently code, we can not multiple user as auditing user. Why?
(Sorry if this topic already has been discussed.)

2. OBJECT auditing does not work before adding acl info to pg_class.rel_acl.
In following situation, pg_audit can not audit OBJECT log.
$ cat postgresql.conf | grep audit
shared_preload_libraries = 'pg_audit'
pg_audit.role = 'hoge_user'
pg_audit.log = 'read, write'
$ psql -d postgres -U hoge_user
=# create table hoge(col int);
=# select * from hoge;
LOG:  AUDIT: SESSION,3,1,READ,SELECT,,,select * from hoge;

OBJECT audit log is not logged here since pg_class.rel_acl is empty
yet. (Only logged SESSION log)
So after creating another unconcerned role and grant any privilege to that user,
OBJECT audit is logged successfully.

=# create role bar_user;
=# grant select on hoge to bar_user;
=# select * from hoge;
LOG:  AUDIT: SESSION,4,1,READ,SELECT,,,select * from hoge;
LOG:  AUDIT: OBJECT,4,1,READ,SELECT,TABLE,public.hoge,select * from hoge;

The both OBJCET and SESSION log are logged.

3. pg_audit logged OBJECT log even EXPLAIN command.
EXPLAIN command does not touch the table actually, but pg_audit writes
audit OBJECT log.
I'm not sure we need to log it. Is it intentional?

Regards,

-------
Sawada Masahiko


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-24 15:38  David Steele <[email protected]>
  parent: Sawada Masahiko <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: David Steele @ 2015-03-24 15:38 UTC (permalink / raw)
  To: Sawada Masahiko <[email protected]>; Alvaro Herrera <[email protected]>; +Cc: Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Hi Sawada,

Thank you for taking the time to look at the patch.

On 3/24/15 10:28 AM, Sawada Masahiko wrote:
> I've applied these patchese successfully.
> 
> I looked into this module, and had a few comments as follows.
> 1. pg_audit audits only one role currently.
> In currently code, we can not multiple user as auditing user. Why?
> (Sorry if this topic already has been discussed.)

There is only one master audit role in a bid for simplicity.  However,
there are two ways you can practically have multiple audit roles (both
are mentioned in the docs):

1) The audit role honors inheritance so you can grant all your audit
roles to the "master" role set in pg_audit.role and all the roles will
be audited.

2) Since pg_audit.role is a GUC, you can set a different audit role per
database by using ALTER DATABASE ... SET.  You can set the GUC per logon
role as well though that would probably make things very complicated.
The GUC is SUSET so normal users cannot tamper with it.

> 2. OBJECT auditing does not work before adding acl info to pg_class.rel_acl.
> In following situation, pg_audit can not audit OBJECT log.
> $ cat postgresql.conf | grep audit
> shared_preload_libraries = 'pg_audit'
> pg_audit.role = 'hoge_user'
> pg_audit.log = 'read, write'
> $ psql -d postgres -U hoge_user
> =# create table hoge(col int);
> =# select * from hoge;
> LOG:  AUDIT: SESSION,3,1,READ,SELECT,,,select * from hoge;
> 
> OBJECT audit log is not logged here since pg_class.rel_acl is empty
> yet. (Only logged SESSION log)
> So after creating another unconcerned role and grant any privilege to that user,
> OBJECT audit is logged successfully.

Yes, object auditing does not work until some grants have been made to
the audit role.

> =# create role bar_user;
> =# grant select on hoge to bar_user;
> =# select * from hoge;
> LOG:  AUDIT: SESSION,4,1,READ,SELECT,,,select * from hoge;
> LOG:  AUDIT: OBJECT,4,1,READ,SELECT,TABLE,public.hoge,select * from hoge;
> 
> The both OBJCET and SESSION log are logged.

Looks right to me.  If you don't want the session logging then disable
pg_audit.log.

Session and object logging are completely independent from each other:
one or the other, or both, or neither can be enabled at any time.

> 3. pg_audit logged OBJECT log even EXPLAIN command.
> EXPLAIN command does not touch the table actually, but pg_audit writes
> audit OBJECT log.
> I'm not sure we need to log it. Is it intentional?

This is intentional.  They are treated as queries since in production
they should be relatively rare (that is, not part of a normal function
or process) and it's good information to have because EXPLAIN can be
used to determine the number of rows in a table, and could also be used
to figure out when data is added or removed from a table.  In essence,
it is a query even if it does not return row data.

If that sounds paranoid, well, auditing is all about paranoia!

-- 
- David Steele
[email protected]



Attachments:

  [application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-25 01:46  Sawada Masahiko <[email protected]>
  parent: David Steele <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: Sawada Masahiko @ 2015-03-25 01:46 UTC (permalink / raw)
  To: David Steele <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Hi David,

Thank you for your answer!

On Wed, Mar 25, 2015 at 12:38 AM, David Steele <[email protected]> wrote:
> Hi Sawada,
>
> Thank you for taking the time to look at the patch.
>
> On 3/24/15 10:28 AM, Sawada Masahiko wrote:
>> I've applied these patchese successfully.
>>
>> I looked into this module, and had a few comments as follows.
>> 1. pg_audit audits only one role currently.
>> In currently code, we can not multiple user as auditing user. Why?
>> (Sorry if this topic already has been discussed.)
>
> There is only one master audit role in a bid for simplicity.  However,
> there are two ways you can practically have multiple audit roles (both
> are mentioned in the docs):
>
> 1) The audit role honors inheritance so you can grant all your audit
> roles to the "master" role set in pg_audit.role and all the roles will
> be audited.
>
> 2) Since pg_audit.role is a GUC, you can set a different audit role per
> database by using ALTER DATABASE ... SET.  You can set the GUC per logon
> role as well though that would probably make things very complicated.
> The GUC is SUSET so normal users cannot tamper with it.

I understood.

>> 2. OBJECT auditing does not work before adding acl info to pg_class.rel_acl.
>> In following situation, pg_audit can not audit OBJECT log.
>> $ cat postgresql.conf | grep audit
>> shared_preload_libraries = 'pg_audit'
>> pg_audit.role = 'hoge_user'
>> pg_audit.log = 'read, write'
>> $ psql -d postgres -U hoge_user
>> =# create table hoge(col int);
>> =# select * from hoge;
>> LOG:  AUDIT: SESSION,3,1,READ,SELECT,,,select * from hoge;
>>
>> OBJECT audit log is not logged here since pg_class.rel_acl is empty
>> yet. (Only logged SESSION log)
>> So after creating another unconcerned role and grant any privilege to that user,
>> OBJECT audit is logged successfully.
>
> Yes, object auditing does not work until some grants have been made to
> the audit role.
>
>> =# create role bar_user;
>> =# grant select on hoge to bar_user;
>> =# select * from hoge;
>> LOG:  AUDIT: SESSION,4,1,READ,SELECT,,,select * from hoge;
>> LOG:  AUDIT: OBJECT,4,1,READ,SELECT,TABLE,public.hoge,select * from hoge;
>>
>> The both OBJCET and SESSION log are logged.
>
> Looks right to me.  If you don't want the session logging then disable
> pg_audit.log.
>
> Session and object logging are completely independent from each other:
> one or the other, or both, or neither can be enabled at any time.

It means that OBJECT log is not logged just after creating table, even
if that table is touched by its owner.
To write OBJECT log, we need to grant privilege to role at least. right?

>> 3. pg_audit logged OBJECT log even EXPLAIN command.
>> EXPLAIN command does not touch the table actually, but pg_audit writes
>> audit OBJECT log.
>> I'm not sure we need to log it. Is it intentional?
>
> This is intentional.  They are treated as queries since in production
> they should be relatively rare (that is, not part of a normal function
> or process) and it's good information to have because EXPLAIN can be
> used to determine the number of rows in a table, and could also be used
> to figure out when data is added or removed from a table.  In essence,
> it is a query even if it does not return row data.

Okay, I understood.

Regards,

-------
Sawada Masahiko


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-25 03:23  David Steele <[email protected]>
  parent: Sawada Masahiko <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: David Steele @ 2015-03-25 03:23 UTC (permalink / raw)
  To: Sawada Masahiko <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

> On Wed, Mar 25, 2015 at 12:38 AM, David Steele <[email protected]> wrote:
>>> 2. OBJECT auditing does not work before adding acl info to pg_class.rel_acl.
>>> In following situation, pg_audit can not audit OBJECT log.
>>> $ cat postgresql.conf | grep audit
>>> shared_preload_libraries = 'pg_audit'
>>> pg_audit.role = 'hoge_user'
>>> pg_audit.log = 'read, write'
>>> $ psql -d postgres -U hoge_user
>>> =# create table hoge(col int);
>>> =# select * from hoge;
>>> LOG:  AUDIT: SESSION,3,1,READ,SELECT,,,select * from hoge;
>>>
>>> OBJECT audit log is not logged here since pg_class.rel_acl is empty
>>> yet. (Only logged SESSION log)
>>> So after creating another unconcerned role and grant any privilege to that user,
>>> OBJECT audit is logged successfully.
>>
>> Yes, object auditing does not work until some grants have been made to
>> the audit role.
>>
>>> =# create role bar_user;
>>> =# grant select on hoge to bar_user;
>>> =# select * from hoge;
>>> LOG:  AUDIT: SESSION,4,1,READ,SELECT,,,select * from hoge;
>>> LOG:  AUDIT: OBJECT,4,1,READ,SELECT,TABLE,public.hoge,select * from hoge;
>>>
>>> The both OBJCET and SESSION log are logged.
>>
>> Looks right to me.  If you don't want the session logging then disable
>> pg_audit.log.
>>
>> Session and object logging are completely independent from each other:
>> one or the other, or both, or neither can be enabled at any time.
> 
> It means that OBJECT log is not logged just after creating table, even
> if that table is touched by its owner.
> To write OBJECT log, we need to grant privilege to role at least. right?

Exactly.  Privileges must be granted to the audit role in order for
object auditing to work.

-- 
- David Steele
[email protected]



Attachments:

  [application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-25 11:46  Sawada Masahiko <[email protected]>
  parent: David Steele <[email protected]>
  0 siblings, 1 reply; 279+ messages in thread

From: Sawada Masahiko @ 2015-03-25 11:46 UTC (permalink / raw)
  To: David Steele <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On Wed, Mar 25, 2015 at 12:23 PM, David Steele <[email protected]> wrote:
>> On Wed, Mar 25, 2015 at 12:38 AM, David Steele <[email protected]> wrote:
>>>> 2. OBJECT auditing does not work before adding acl info to pg_class.rel_acl.
>>>> In following situation, pg_audit can not audit OBJECT log.
>>>> $ cat postgresql.conf | grep audit
>>>> shared_preload_libraries = 'pg_audit'
>>>> pg_audit.role = 'hoge_user'
>>>> pg_audit.log = 'read, write'
>>>> $ psql -d postgres -U hoge_user
>>>> =# create table hoge(col int);
>>>> =# select * from hoge;
>>>> LOG:  AUDIT: SESSION,3,1,READ,SELECT,,,select * from hoge;
>>>>
>>>> OBJECT audit log is not logged here since pg_class.rel_acl is empty
>>>> yet. (Only logged SESSION log)
>>>> So after creating another unconcerned role and grant any privilege to that user,
>>>> OBJECT audit is logged successfully.
>>>
>>> Yes, object auditing does not work until some grants have been made to
>>> the audit role.
>>>
>>>> =# create role bar_user;
>>>> =# grant select on hoge to bar_user;
>>>> =# select * from hoge;
>>>> LOG:  AUDIT: SESSION,4,1,READ,SELECT,,,select * from hoge;
>>>> LOG:  AUDIT: OBJECT,4,1,READ,SELECT,TABLE,public.hoge,select * from hoge;
>>>>
>>>> The both OBJCET and SESSION log are logged.
>>>
>>> Looks right to me.  If you don't want the session logging then disable
>>> pg_audit.log.
>>>
>>> Session and object logging are completely independent from each other:
>>> one or the other, or both, or neither can be enabled at any time.
>>
>> It means that OBJECT log is not logged just after creating table, even
>> if that table is touched by its owner.
>> To write OBJECT log, we need to grant privilege to role at least. right?
>
> Exactly.  Privileges must be granted to the audit role in order for
> object auditing to work.
>

The table owner always can touch its table.
So does it lead that table owner can get its table information while
hiding OBJECT logging?

Also I looked into latest patch again.
Here are two review comment.

1.
> typedef struct
> {
>    int64 statementId;
>   int64 substatementId;
Both statementId and substatementId could be negative number.
I think these should be uint64 instead.

2.
I got ERROR when executing function uses cursor.

1) create empty table (hoge table)
2) create test function as follows.

create function test() returns int as $$
declare
    cur1 cursor for select * from hoge;
    tmp int;
begin
    open cur1;
    fetch cur1 into tmp;
   return tmp;
end$$
language plpgsql ;

3) execute test function (got ERROR)
=# select test();
LOG:  AUDIT: SESSION,6,1,READ,SELECT,,,selecT test();
LOG:  AUDIT: SESSION,6,2,FUNCTION,EXECUTE,FUNCTION,public.test,selecT test();
LOG:  AUDIT: SESSION,6,3,READ,SELECT,,,select * from hoge
CONTEXT:  PL/pgSQL function test() line 6 at OPEN
ERROR:  pg_audit stack is already empty
STATEMENT:  selecT test();

It seems like that the item in stack is already freed by deleting
pg_audit memory context (in MemoryContextDelete()),
before calling stack_pop in dropping of top-level Portal.

Regards,

-------
Sawada Masahiko


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* Re: Auditing extension for PostgreSQL (Take 2)
@ 2015-03-25 13:24  David Steele <[email protected]>
  parent: Sawada Masahiko <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: David Steele @ 2015-03-25 13:24 UTC (permalink / raw)
  To: Sawada Masahiko <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Abhijit Menon-Sen <[email protected]>; Stephen Frost <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 3/25/15 7:46 AM, Sawada Masahiko wrote:
> On Wed, Mar 25, 2015 at 12:23 PM, David Steele <[email protected]> wrote:
>>> On Wed, Mar 25, 2015 at 12:38 AM, David Steele <[email protected]> wrote:
>>>>> 2. OBJECT auditing does not work before adding acl info to pg_class.rel_acl.
>>>>> In following situation, pg_audit can not audit OBJECT log.
>>>>> $ cat postgresql.conf | grep audit
>>>>> shared_preload_libraries = 'pg_audit'
>>>>> pg_audit.role = 'hoge_user'
>>>>> pg_audit.log = 'read, write'
>>>>> $ psql -d postgres -U hoge_user
>>>>> =# create table hoge(col int);
>>>>> =# select * from hoge;
>>>>> LOG:  AUDIT: SESSION,3,1,READ,SELECT,,,select * from hoge;
>>>>>
>>>>> OBJECT audit log is not logged here since pg_class.rel_acl is empty
>>>>> yet. (Only logged SESSION log)
>>>>> So after creating another unconcerned role and grant any privilege to that user,
>>>>> OBJECT audit is logged successfully.
>>>>
>>>> Yes, object auditing does not work until some grants have been made to
>>>> the audit role.
>>>>
>>>>> =# create role bar_user;
>>>>> =# grant select on hoge to bar_user;
>>>>> =# select * from hoge;
>>>>> LOG:  AUDIT: SESSION,4,1,READ,SELECT,,,select * from hoge;
>>>>> LOG:  AUDIT: OBJECT,4,1,READ,SELECT,TABLE,public.hoge,select * from hoge;
>>>>>
>>>>> The both OBJCET and SESSION log are logged.
>>>>
>>>> Looks right to me.  If you don't want the session logging then disable
>>>> pg_audit.log.
>>>>
>>>> Session and object logging are completely independent from each other:
>>>> one or the other, or both, or neither can be enabled at any time.
>>>
>>> It means that OBJECT log is not logged just after creating table, even
>>> if that table is touched by its owner.
>>> To write OBJECT log, we need to grant privilege to role at least. right?
>>
>> Exactly.  Privileges must be granted to the audit role in order for
>> object auditing to work.
>>
> 
> The table owner always can touch its table.
> So does it lead that table owner can get its table information while
> hiding OBJECT logging?

Yes, the table owner would be able to access the table without object
logging if grants to that table were not made to the audit role.  That
would also be true for any other user that had grants on the table.

The purpose of object auditing is to allow more fine-grained control and
is intended to be used in situations where you only want to audit some
things, rather than all things.  Logging everything is better done with
the session logging.

However, object logging does yield more information since it lists every
table that was touched by the statement, so there may be cases where
you'd like to object log everything.  In that case I'd recommend writing
a bit of plpgsql code to create the grants.

> Also I looked into latest patch again.
> Here are two review comment.
> 
> 1.
>> typedef struct
>> {
>>    int64 statementId;
>>   int64 substatementId;
> Both statementId and substatementId could be negative number.
> I think these should be uint64 instead.

True.  I did this because printf formatting for uint64 seems to be vary
across platforms.  int64 formatting is more standard and still gives
more than enough IDs.

I could change it back to uint64 if you have a portable way to modify
the sprintf at line 507.

> 2.
> I got ERROR when executing function uses cursor.
> 
> 1) create empty table (hoge table)
> 2) create test function as follows.
> 
> create function test() returns int as $$
> declare
>     cur1 cursor for select * from hoge;
>     tmp int;
> begin
>     open cur1;
>     fetch cur1 into tmp;
>    return tmp;
> end$$
> language plpgsql ;
> 
> 3) execute test function (got ERROR)
> =# select test();
> LOG:  AUDIT: SESSION,6,1,READ,SELECT,,,selecT test();
> LOG:  AUDIT: SESSION,6,2,FUNCTION,EXECUTE,FUNCTION,public.test,selecT test();
> LOG:  AUDIT: SESSION,6,3,READ,SELECT,,,select * from hoge
> CONTEXT:  PL/pgSQL function test() line 6 at OPEN
> ERROR:  pg_audit stack is already empty
> STATEMENT:  selecT test();
> 
> It seems like that the item in stack is already freed by deleting
> pg_audit memory context (in MemoryContextDelete()),
> before calling stack_pop in dropping of top-level Portal.

Good catch, I'll add this to my test cases and work on a fix.  I think I
see a good way to approach it.

-- 
- David Steele
[email protected]



Attachments:

  [application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v1] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--Dqk2D9QbcLZswiIf--





^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v1] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--Dqk2D9QbcLZswiIf--





^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread

* [PATCH v6 1/2] Clear padding in PgStat_HashKey keys
@ 2024-11-02 14:21  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 279+ messages in thread

From: Bertrand Drouvot @ 2024-11-02 14:21 UTC (permalink / raw)

PgStat_HashKey keys are currently initialized in a way that could result in random
data in the padding bytes (if there was padding in PgStat_HashKey which is not
the case currently).

We are using sizeof(PgStat_HashKey) in pgstat_cmp_hash_key() and we compute the
hash hash key in pgstat_hash_hash_key() using the PgStat_HashKey struct size as
input. So, we have to ensure that no random data can be stored in the padding
bytes (if any) of a PgStat_HashKey key.
---
 src/backend/utils/activity/pgstat.c       |  3 +++
 src/backend/utils/activity/pgstat_shmem.c | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index be48432cc3..ea8c5691e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -938,6 +938,9 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 
 	pgstat_prep_snapshot();
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objid = objid;
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index a09c6fee05..c1b7ff76b1 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -432,11 +432,18 @@ PgStat_EntryRef *
 pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
 					 bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/*
 	 * passing in created_entry only makes sense if we possibly could create
 	 * entry.
@@ -908,10 +915,17 @@ pgstat_drop_database_and_contents(Oid dboid)
 bool
 pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objid = objid};
+	PgStat_HashKey key;
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
+	/* clear padding */
+	memset(&key, 0, sizeof(struct PgStat_HashKey));
+
+	key.kind = kind;
+	key.dboid = dboid;
+	key.objid = objid;
+
 	/* delete local reference */
 	if (pgStatEntryRefHash)
 	{
-- 
2.34.1


--gvmrl0mfoTEJEfPE
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v6-0002-Provide-relfilenode-statistics.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 279+ messages in thread


end of thread, other threads:[~2024-11-02 14:21 UTC | newest]

Thread overview: 279+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2015-02-15 02:34 Auditing extension for PostgreSQL (Take 2) David Steele <[email protected]>
2015-02-17 15:34 ` Stephen Frost <[email protected]>
2015-02-23 15:59   ` David Steele <[email protected]>
2015-02-24 16:22     ` David Steele <[email protected]>
2015-03-23 05:31       ` Abhijit Menon-Sen <[email protected]>
2015-03-23 16:40         ` David Steele <[email protected]>
2015-03-23 17:39           ` Sawada Masahiko <[email protected]>
2015-03-23 18:17             ` Alvaro Herrera <[email protected]>
2015-03-24 14:28               ` Sawada Masahiko <[email protected]>
2015-03-24 15:38                 ` David Steele <[email protected]>
2015-03-25 01:46                   ` Sawada Masahiko <[email protected]>
2015-03-25 03:23                     ` David Steele <[email protected]>
2015-03-25 11:46                       ` Sawada Masahiko <[email protected]>
2015-03-25 13:24                         ` David Steele <[email protected]>
2015-03-23 18:30             ` David Steele <[email protected]>
2015-02-18 13:25 ` Simon Riggs <[email protected]>
2015-02-18 15:07   ` David Steele <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v1] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v1] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>
2024-11-02 14:21 [PATCH v6 1/2] Clear padding in PgStat_HashKey keys Bertrand Drouvot <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox