diff --git a/contrib/Makefile b/contrib/Makefile
index f84e684972dacc72c5432bcb5f01384e8b0c2f01..1f3d3f11852e2db511d100c8ba97a7ff40f6e399 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -28,6 +28,7 @@ SUBDIRS = \
 		oid2name	\
 		pageinspect	\
 		passwordcheck	\
+		pg_audit	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
diff --git a/contrib/pg_audit/.gitignore b/contrib/pg_audit/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a5267cf5b2107533eba415210b24e45874c9a75f
--- /dev/null
+++ b/contrib/pg_audit/.gitignore
@@ -0,0 +1,5 @@
+log/
+results/
+tmp_check/
+regression.diffs
+regression.out
diff --git a/contrib/pg_audit/Makefile b/contrib/pg_audit/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..7b360110a8ff8c2d6aefa44042e04be57a48e2be
--- /dev/null
+++ b/contrib/pg_audit/Makefile
@@ -0,0 +1,21 @@
+# pg_audit/Makefile
+
+MODULE = pg_audit
+MODULE_big = pg_audit
+OBJS = pg_audit.o
+
+EXTENSION = pg_audit
+REGRESS = pg_audit
+REGRESS_OPTS = --temp-config=$(top_srcdir)/contrib/pg_audit/pg_audit.conf
+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/expected/pg_audit.out b/contrib/pg_audit/expected/pg_audit.out
new file mode 100644
index 0000000000000000000000000000000000000000..af4609b9bae33377c455aaa4f1e83f18ef92f048
--- /dev/null
+++ b/contrib/pg_audit/expected/pg_audit.out
@@ -0,0 +1,964 @@
+-- Load pg_audit module
+create extension pg_audit;
+--
+-- Audit log fields are:
+--     AUDIT_TYPE - SESSION or OBJECT
+--     STATEMENT_ID - ID of the statement in the current backend
+--     SUBSTATEMENT_ID - ID of the substatement in the current backend
+--     CLASS - Class of statement being logged (e.g. ROLE, READ, WRITE)
+--     COMMAND - e.g. SELECT, CREATE ROLE, UPDATE
+--     OBJECT_TYPE - When available, type of object acted on (e.g. TABLE, VIEW)
+--     OBJECT_NAME - When available, fully-qualified table of object
+--     STATEMENT - The statement being logged
+--     PARAMETER - If parameter logging is requested, they will follow the
+--                 statement
+--
+-- Create a superuser role that we know the name of for testing
+CREATE USER super SUPERUSER;
+ALTER ROLE super SET pg_audit.log = 'Role';
+ALTER ROLE super SET pg_audit.log_level = 'notice';
+\connect contrib_regression super;
+--
+-- Create auditor role
+CREATE ROLE auditor;
+NOTICE:  AUDIT: SESSION,1,1,ROLE,CREATE ROLE,,,CREATE ROLE auditor;,<not logged>
+--
+-- Create first test user
+CREATE USER user1;
+NOTICE:  AUDIT: SESSION,2,1,ROLE,CREATE ROLE,,,CREATE USER user1;,<not logged>
+ALTER ROLE user1 SET pg_audit.log = 'ddl, ROLE';
+NOTICE:  AUDIT: SESSION,3,1,ROLE,ALTER ROLE,,,"ALTER ROLE user1 SET pg_audit.log = 'ddl, ROLE';",<not logged>
+ALTER ROLE user1 SET pg_audit.log_level = 'notice';
+NOTICE:  AUDIT: SESSION,4,1,ROLE,ALTER ROLE,,,ALTER ROLE user1 SET pg_audit.log_level = 'notice';,<not logged>
+--
+-- Create, select, drop (select will not be audited)
+\connect contrib_regression user1
+CREATE TABLE public.test (id INT);
+NOTICE:  AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.test,CREATE TABLE public.test (id INT);,<not logged>
+SELECT * FROM test;
+ id 
+----
+(0 rows)
+
+DROP TABLE test;
+NOTICE:  AUDIT: SESSION,2,1,DDL,DROP TABLE,TABLE,public.test,DROP TABLE test;,<not logged>
+--
+-- Create second test user
+\connect contrib_regression super
+CREATE USER user2;
+NOTICE:  AUDIT: SESSION,1,1,ROLE,CREATE ROLE,,,CREATE USER user2;,<not logged>
+ALTER ROLE user2 SET pg_audit.log = 'Read, writE';
+NOTICE:  AUDIT: SESSION,2,1,ROLE,ALTER ROLE,,,"ALTER ROLE user2 SET pg_audit.log = 'Read, writE';",<not logged>
+ALTER ROLE user2 SET pg_audit.log_catalog = OFF;
+NOTICE:  AUDIT: SESSION,3,1,ROLE,ALTER ROLE,,,ALTER ROLE user2 SET pg_audit.log_catalog = OFF;,<not logged>
+ALTER ROLE user2 SET pg_audit.log_level = 'warning';
+NOTICE:  AUDIT: SESSION,4,1,ROLE,ALTER ROLE,,,ALTER ROLE user2 SET pg_audit.log_level = 'warning';,<not logged>
+ALTER ROLE user2 SET pg_audit.role = auditor;
+NOTICE:  AUDIT: SESSION,5,1,ROLE,ALTER ROLE,,,ALTER ROLE user2 SET pg_audit.role = auditor;,<not logged>
+ALTER ROLE user2 SET pg_audit.log_statement_once = ON;
+NOTICE:  AUDIT: SESSION,6,1,ROLE,ALTER ROLE,,,ALTER ROLE user2 SET pg_audit.log_statement_once = ON;,<not logged>
+\connect contrib_regression user2
+CREATE TABLE test2 (id INT);
+GRANT SELECT ON TABLE public.test2 TO auditor;
+--
+-- Role-based tests
+CREATE TABLE test3
+(
+	id INT
+);
+SELECT count(*)
+  FROM
+(
+	SELECT relname
+	  FROM pg_class
+	  LIMIT 1
+) SUBQUERY;
+ count 
+-------
+     1
+(1 row)
+
+SELECT *
+  FROM test3, test2;
+WARNING:  AUDIT: SESSION,1,1,READ,SELECT,,,"SELECT *
+  FROM test3, test2;",<not logged>
+WARNING:  AUDIT: OBJECT,1,1,READ,SELECT,TABLE,public.test2,<previously logged>,<previously logged>
+ id | id 
+----+----
+(0 rows)
+
+GRANT INSERT
+   ON TABLE public.test3
+   TO auditor;
+--
+-- Create a view to test logging
+CREATE VIEW vw_test3 AS
+SELECT *
+  FROM test3;
+GRANT SELECT
+   ON vw_test3
+   TO auditor;
+--
+-- Object logged because of:
+-- select on vw_test3
+-- select on test2
+SELECT *
+  FROM vw_test3, test2;
+WARNING:  AUDIT: SESSION,2,1,READ,SELECT,,,"SELECT *
+  FROM vw_test3, test2;",<not logged>
+WARNING:  AUDIT: OBJECT,2,1,READ,SELECT,TABLE,public.test2,<previously logged>,<previously logged>
+WARNING:  AUDIT: OBJECT,2,1,READ,SELECT,VIEW,public.vw_test3,<previously logged>,<previously logged>
+ id | id 
+----+----
+(0 rows)
+
+--
+-- Object logged because of:
+-- insert on test3
+-- select on test2
+WITH CTE AS
+(
+	SELECT id
+	  FROM test2
+)
+INSERT INTO test3
+SELECT id
+  FROM cte;
+WARNING:  AUDIT: SESSION,3,1,WRITE,INSERT,,,"WITH CTE AS
+(
+	SELECT id
+	  FROM test2
+)
+INSERT INTO test3
+SELECT id
+  FROM cte;",<not logged>
+WARNING:  AUDIT: OBJECT,3,1,WRITE,INSERT,TABLE,public.test3,<previously logged>,<previously logged>
+WARNING:  AUDIT: OBJECT,3,1,READ,SELECT,TABLE,public.test2,<previously logged>,<previously logged>
+--
+-- Object logged because of:
+-- insert on test3
+WITH CTE AS
+(
+	INSERT INTO test3 VALUES (1)
+				   RETURNING id
+)
+INSERT INTO test2
+SELECT id
+  FROM cte;
+WARNING:  AUDIT: SESSION,4,1,WRITE,INSERT,,,"WITH CTE AS
+(
+	INSERT INTO test3 VALUES (1)
+				   RETURNING id
+)
+INSERT INTO test2
+SELECT id
+  FROM cte;",<not logged>
+WARNING:  AUDIT: OBJECT,4,1,WRITE,INSERT,TABLE,public.test3,<previously logged>,<previously logged>
+GRANT UPDATE ON TABLE public.test2 TO auditor;
+--
+-- Object logged because of:
+-- insert on test3
+-- update on test2
+WITH CTE AS
+(
+	UPDATE test2
+	   SET id = 1
+	RETURNING id
+)
+INSERT INTO test3
+SELECT id
+  FROM cte;
+WARNING:  AUDIT: SESSION,5,1,WRITE,INSERT,,,"WITH CTE AS
+(
+	UPDATE test2
+	   SET id = 1
+	RETURNING id
+)
+INSERT INTO test3
+SELECT id
+  FROM cte;",<not logged>
+WARNING:  AUDIT: OBJECT,5,1,WRITE,INSERT,TABLE,public.test3,<previously logged>,<previously logged>
+WARNING:  AUDIT: OBJECT,5,1,WRITE,UPDATE,TABLE,public.test2,<previously logged>,<previously logged>
+--
+-- Object logged because of:
+-- insert on test2
+WITH CTE AS
+(
+	INSERT INTO test2 VALUES (1)
+				   RETURNING id
+)
+UPDATE test3
+   SET id = cte.id
+  FROM cte
+ WHERE test3.id <> cte.id;
+WARNING:  AUDIT: SESSION,6,1,WRITE,UPDATE,,,"WITH CTE AS
+(
+	INSERT INTO test2 VALUES (1)
+				   RETURNING id
+)
+UPDATE test3
+   SET id = cte.id
+  FROM cte
+ WHERE test3.id <> cte.id;",<not logged>
+WARNING:  AUDIT: OBJECT,6,1,WRITE,INSERT,TABLE,public.test2,<previously logged>,<previously logged>
+--
+-- Change permissions of user 2 so that only object logging will be done
+\connect contrib_regression super
+alter role user2 set pg_audit.log = 'NONE';
+NOTICE:  AUDIT: SESSION,1,1,ROLE,ALTER ROLE,,,alter role user2 set pg_audit.log = 'NONE';,<not logged>
+\connect contrib_regression user2
+--
+-- Create test4 and add permissions
+CREATE TABLE test4
+(
+	id int,
+	name text
+);
+GRANT SELECT (name)
+   ON TABLE public.test4
+   TO auditor;
+GRANT UPDATE (id)
+   ON TABLE public.test4
+   TO auditor;
+GRANT insert (name)
+   ON TABLE public.test4
+   TO auditor;
+--
+-- Not object logged
+SELECT id
+  FROM public.test4;
+ id 
+----
+(0 rows)
+
+--
+-- Object logged because of:
+-- select (name) on test4
+SELECT name
+  FROM public.test4;
+WARNING:  AUDIT: OBJECT,1,1,READ,SELECT,TABLE,public.test4,"SELECT name
+  FROM public.test4;",<not logged>
+ name 
+------
+(0 rows)
+
+--
+-- Not object logged
+INSERT INTO public.test4 (id)
+				  VALUES (1);
+--
+-- Object logged because of:
+-- insert (name) on test4
+INSERT INTO public.test4 (name)
+				  VALUES ('test');
+WARNING:  AUDIT: OBJECT,2,1,WRITE,INSERT,TABLE,public.test4,"INSERT INTO public.test4 (name)
+				  VALUES ('test');",<not logged>
+--
+-- Not object logged
+UPDATE public.test4
+   SET name = 'foo';
+--
+-- Object logged because of:
+-- update (id) on test4
+UPDATE public.test4
+   SET id = 1;
+WARNING:  AUDIT: OBJECT,3,1,WRITE,UPDATE,TABLE,public.test4,"UPDATE public.test4
+   SET id = 1;",<not logged>
+--
+-- Object logged because of:
+-- update (name) on test4
+-- update (name) takes precedence over select (name) due to ordering
+update public.test4 set name = 'foo' where name = 'bar';
+WARNING:  AUDIT: OBJECT,4,1,WRITE,UPDATE,TABLE,public.test4,update public.test4 set name = 'foo' where name = 'bar';,<not logged>
+--
+-- Drop test tables
+DROP TABLE test2;
+DROP VIEW vw_test3;
+DROP TABLE test3;
+DROP TABLE test4;
+--
+-- Change permissions of user 1 so that session logging will be done
+\connect contrib_regression super
+alter role user1 set pg_audit.log = 'DDL, READ';
+NOTICE:  AUDIT: SESSION,1,1,ROLE,ALTER ROLE,,,"alter role user1 set pg_audit.log = 'DDL, READ';",<not logged>
+\connect contrib_regression user1
+--
+-- Create table is session logged
+CREATE TABLE public.account
+(
+	id INT,
+	name TEXT,
+	password TEXT,
+	description TEXT
+);
+NOTICE:  AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.account,"CREATE TABLE public.account
+(
+	id INT,
+	name TEXT,
+	password TEXT,
+	description TEXT
+);",<not logged>
+--
+-- Select is session logged
+SELECT *
+  FROM account;
+NOTICE:  AUDIT: SESSION,2,1,READ,SELECT,,,"SELECT *
+  FROM account;",<not logged>
+ id | name | password | description 
+----+------+----------+-------------
+(0 rows)
+
+--
+-- Insert is not logged
+INSERT INTO account (id, name, password, description)
+			 VALUES (1, 'user1', 'HASH1', 'blah, blah');
+--
+-- Change permissions of user 1 so that only object logging will be done
+\connect contrib_regression super
+alter role user1 set pg_audit.log = 'none';
+NOTICE:  AUDIT: SESSION,1,1,ROLE,ALTER ROLE,,,alter role user1 set pg_audit.log = 'none';,<not logged>
+alter role user1 set pg_audit.role = 'auditor';
+NOTICE:  AUDIT: SESSION,2,1,ROLE,ALTER ROLE,,,alter role user1 set pg_audit.role = 'auditor';,<not logged>
+\connect contrib_regression user1
+--
+-- ROLE class not set, so auditor grants not logged
+GRANT SELECT (password),
+	  UPDATE (name, password)
+   ON TABLE public.account
+   TO auditor;
+--
+-- Not object logged
+SELECT id,
+	   name
+  FROM account;
+ id | name  
+----+-------
+  1 | user1
+(1 row)
+
+--
+-- Object logged because of:
+-- select (password) on account
+SELECT password
+  FROM account;
+NOTICE:  AUDIT: OBJECT,1,1,READ,SELECT,TABLE,public.account,"SELECT password
+  FROM account;",<not logged>
+ password 
+----------
+ HASH1
+(1 row)
+
+--
+-- Not object logged
+UPDATE account
+   SET description = 'yada, yada';
+--
+-- Object logged because of:
+-- update (password) on account
+UPDATE account
+   SET password = 'HASH2';
+NOTICE:  AUDIT: OBJECT,2,1,WRITE,UPDATE,TABLE,public.account,"UPDATE account
+   SET password = 'HASH2';",<not logged>
+--
+-- Change permissions of user 1 so that session relation logging will be done
+\connect contrib_regression super
+alter role user1 set pg_audit.log_relation = on;
+NOTICE:  AUDIT: SESSION,1,1,ROLE,ALTER ROLE,,,alter role user1 set pg_audit.log_relation = on;,<not logged>
+alter role user1 set pg_audit.log = 'read, WRITE';
+NOTICE:  AUDIT: SESSION,2,1,ROLE,ALTER ROLE,,,"alter role user1 set pg_audit.log = 'read, WRITE';",<not logged>
+\connect contrib_regression user1
+--
+-- Not logged
+create table ACCOUNT_ROLE_MAP
+(
+	account_id INT,
+	role_id INT
+);
+--
+-- ROLE class not set, so auditor grants not logged
+GRANT SELECT
+   ON TABLE public.account_role_map
+   TO auditor;
+--
+-- Object logged because of:
+-- select (password) on account
+-- select on account_role_map
+-- Session logged on all tables because log = read and log_relation = on
+SELECT account.password,
+	   account_role_map.role_id
+  FROM account
+	   INNER JOIN account_role_map
+			on account.id = account_role_map.account_id;
+NOTICE:  AUDIT: OBJECT,1,1,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;",<not logged>
+NOTICE:  AUDIT: SESSION,1,1,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;",<not logged>
+NOTICE:  AUDIT: OBJECT,1,1,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;",<not logged>
+NOTICE:  AUDIT: SESSION,1,1,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;",<not logged>
+ password | role_id 
+----------+---------
+(0 rows)
+
+--
+-- Object logged because of:
+-- select (password) on account
+-- Session logged on all tables because log = read and log_relation = on
+SELECT password
+  FROM account;
+NOTICE:  AUDIT: OBJECT,2,1,READ,SELECT,TABLE,public.account,"SELECT password
+  FROM account;",<not logged>
+NOTICE:  AUDIT: SESSION,2,1,READ,SELECT,TABLE,public.account,"SELECT password
+  FROM account;",<not logged>
+ password 
+----------
+ HASH2
+(1 row)
+
+--
+-- Not object logged
+-- Session logged on all tables because log = read and log_relation = on
+UPDATE account
+   SET description = 'yada, yada';
+NOTICE:  AUDIT: SESSION,3,1,WRITE,UPDATE,TABLE,public.account,"UPDATE account
+   SET description = 'yada, yada';",<not logged>
+--
+-- Object logged because of:
+-- select (password) on account (in the where clause)
+-- Session logged on all tables because log = read and log_relation = on
+UPDATE account
+   SET description = 'yada, yada'
+ where password = 'HASH2';
+NOTICE:  AUDIT: OBJECT,4,1,WRITE,UPDATE,TABLE,public.account,"UPDATE account
+   SET description = 'yada, yada'
+ where password = 'HASH2';",<not logged>
+NOTICE:  AUDIT: SESSION,4,1,WRITE,UPDATE,TABLE,public.account,"UPDATE account
+   SET description = 'yada, yada'
+ where password = 'HASH2';",<not logged>
+--
+-- Object logged because of:
+-- update (password) on account
+-- Session logged on all tables because log = read and log_relation = on
+UPDATE account
+   SET password = 'HASH2';
+NOTICE:  AUDIT: OBJECT,5,1,WRITE,UPDATE,TABLE,public.account,"UPDATE account
+   SET password = 'HASH2';",<not logged>
+NOTICE:  AUDIT: SESSION,5,1,WRITE,UPDATE,TABLE,public.account,"UPDATE account
+   SET password = 'HASH2';",<not logged>
+--
+-- Change back to superuser to do exhaustive tests
+\connect contrib_regression super
+SET pg_audit.log = 'ALL';
+NOTICE:  AUDIT: SESSION,1,1,MISC,SET,,,SET pg_audit.log = 'ALL';,<not logged>
+SET pg_audit.log_level = 'notice';
+NOTICE:  AUDIT: SESSION,2,1,MISC,SET,,,SET pg_audit.log_level = 'notice';,<not logged>
+SET pg_audit.log_relation = ON;
+NOTICE:  AUDIT: SESSION,3,1,MISC,SET,,,SET pg_audit.log_relation = ON;,<not logged>
+SET pg_audit.log_parameter = ON;
+NOTICE:  AUDIT: SESSION,4,1,MISC,SET,,,SET pg_audit.log_parameter = ON;,<none>
+--
+-- Simple DO block
+DO $$
+BEGIN
+	raise notice 'test';
+END $$;
+NOTICE:  AUDIT: SESSION,5,1,FUNCTION,DO,,,"DO $$
+BEGIN
+	raise notice 'test';
+END $$;",<none>
+NOTICE:  test
+--
+-- Create test schema
+CREATE SCHEMA test;
+NOTICE:  AUDIT: SESSION,6,1,DDL,CREATE SCHEMA,SCHEMA,test,CREATE SCHEMA test;,<none>
+--
+-- Copy account to stdout
+COPY account TO stdout;
+NOTICE:  AUDIT: SESSION,7,1,READ,SELECT,TABLE,public.account,COPY account TO stdout;,<none>
+1	user1	HASH2	yada, yada
+--
+-- Create a table from a query
+CREATE TABLE test.account_copy AS
+SELECT *
+  FROM account;
+NOTICE:  AUDIT: SESSION,8,1,READ,SELECT,TABLE,public.account,"CREATE TABLE test.account_copy AS
+SELECT *
+  FROM account;",<none>
+NOTICE:  AUDIT: SESSION,8,1,WRITE,INSERT,TABLE,test.account_copy,"CREATE TABLE test.account_copy AS
+SELECT *
+  FROM account;",<none>
+NOTICE:  AUDIT: SESSION,8,2,DDL,CREATE TABLE AS,TABLE,test.account_copy,"CREATE TABLE test.account_copy AS
+SELECT *
+  FROM account;",<none>
+--
+-- Copy from stdin to account copy
+COPY test.account_copy from stdin;
+NOTICE:  AUDIT: SESSION,9,1,WRITE,INSERT,TABLE,test.account_copy,COPY test.account_copy from stdin;,<none>
+--
+-- Test prepared statement
+PREPARE pgclassstmt (oid) AS
+SELECT *
+  FROM account
+ WHERE id = $1;
+NOTICE:  AUDIT: SESSION,10,1,READ,PREPARE,,,"PREPARE pgclassstmt (oid) AS
+SELECT *
+  FROM account
+ WHERE id = $1;",<none>
+EXECUTE pgclassstmt (1);
+NOTICE:  AUDIT: SESSION,11,1,READ,SELECT,TABLE,public.account,"PREPARE pgclassstmt (oid) AS
+SELECT *
+  FROM account
+ WHERE id = $1;",1
+NOTICE:  AUDIT: SESSION,11,2,MISC,EXECUTE,,,EXECUTE pgclassstmt (1);,<none>
+ id | name  | password | description 
+----+-------+----------+-------------
+  1 | user1 | HASH2    | yada, yada
+(1 row)
+
+DEALLOCATE pgclassstmt;
+NOTICE:  AUDIT: SESSION,12,1,MISC,DEALLOCATE,,,DEALLOCATE pgclassstmt;,<none>
+--
+-- Test cursor
+BEGIN;
+NOTICE:  AUDIT: SESSION,13,1,MISC,BEGIN,,,BEGIN;,<none>
+DECLARE ctest SCROLL CURSOR FOR
+SELECT count(*)
+  FROM
+(
+	SELECT relname
+	  FROM pg_class
+	 LIMIT 1
+ ) subquery;
+NOTICE:  AUDIT: SESSION,14,1,READ,SELECT,TABLE,pg_catalog.pg_class,"DECLARE ctest SCROLL CURSOR FOR
+SELECT count(*)
+  FROM
+(
+	SELECT relname
+	  FROM pg_class
+	 LIMIT 1
+ ) subquery;",<none>
+NOTICE:  AUDIT: SESSION,14,2,READ,DECLARE CURSOR,,,"DECLARE ctest SCROLL CURSOR FOR
+SELECT count(*)
+  FROM
+(
+	SELECT relname
+	  FROM pg_class
+	 LIMIT 1
+ ) subquery;",<none>
+FETCH NEXT FROM ctest;
+NOTICE:  AUDIT: SESSION,15,1,MISC,FETCH,,,FETCH NEXT FROM ctest;,<none>
+ count 
+-------
+     1
+(1 row)
+
+CLOSE ctest;
+NOTICE:  AUDIT: SESSION,16,1,MISC,CLOSE CURSOR,,,CLOSE ctest;,<none>
+COMMIT;
+NOTICE:  AUDIT: SESSION,17,1,MISC,COMMIT,,,COMMIT;,<none>
+--
+-- Turn off log_catalog and pg_class will not be logged
+SET pg_audit.log_catalog = OFF;
+NOTICE:  AUDIT: SESSION,18,1,MISC,SET,,,SET pg_audit.log_catalog = OFF;,<none>
+SELECT count(*)
+  FROM
+(
+	SELECT relname
+	  FROM pg_class
+	 LIMIT 1
+ ) subquery;
+ count 
+-------
+     1
+(1 row)
+
+--
+-- Test prepared insert
+CREATE TABLE test.test_insert
+(
+	id INT
+);
+NOTICE:  AUDIT: SESSION,19,1,DDL,CREATE TABLE,TABLE,test.test_insert,"CREATE TABLE test.test_insert
+(
+	id INT
+);",<none>
+PREPARE pgclassstmt (oid) AS
+INSERT INTO test.test_insert (id)
+					  VALUES ($1);
+NOTICE:  AUDIT: SESSION,20,1,WRITE,PREPARE,,,"PREPARE pgclassstmt (oid) AS
+INSERT INTO test.test_insert (id)
+					  VALUES ($1);",<none>
+EXECUTE pgclassstmt (1);
+NOTICE:  AUDIT: SESSION,21,1,WRITE,INSERT,TABLE,test.test_insert,"PREPARE pgclassstmt (oid) AS
+INSERT INTO test.test_insert (id)
+					  VALUES ($1);",1
+NOTICE:  AUDIT: SESSION,21,2,MISC,EXECUTE,,,EXECUTE pgclassstmt (1);,<none>
+--
+-- Check that primary key creation is logged
+CREATE TABLE public.test
+(
+	id INT,
+	name TEXT,
+	description TEXT,
+	CONSTRAINT test_pkey PRIMARY KEY (id)
+);
+NOTICE:  AUDIT: SESSION,22,1,DDL,CREATE TABLE,TABLE,public.test,"CREATE TABLE public.test
+(
+	id INT,
+	name TEXT,
+	description TEXT,
+	CONSTRAINT test_pkey PRIMARY KEY (id)
+);",<none>
+NOTICE:  AUDIT: SESSION,22,1,DDL,CREATE TABLE,INDEX,public.test_pkey,"CREATE TABLE public.test
+(
+	id INT,
+	name TEXT,
+	description TEXT,
+	CONSTRAINT test_pkey PRIMARY KEY (id)
+);",<none>
+--
+-- Check that analyze is logged
+ANALYZE test;
+NOTICE:  AUDIT: SESSION,23,1,MISC,ANALYZE,,,ANALYZE test;,<none>
+--
+-- Grants to public should not cause object logging (session logging will
+-- still happen)
+GRANT SELECT
+  ON TABLE public.test
+  TO PUBLIC;
+NOTICE:  AUDIT: SESSION,24,1,ROLE,GRANT,TABLE,,"GRANT SELECT
+  ON TABLE public.test
+  TO PUBLIC;",<none>
+SELECT *
+  FROM test;
+NOTICE:  AUDIT: SESSION,25,1,READ,SELECT,TABLE,public.test,"SELECT *
+  FROM test;",<none>
+ id | name | description 
+----+------+-------------
+(0 rows)
+
+-- Check that statements without columns log
+SELECT
+  FROM test;
+NOTICE:  AUDIT: SESSION,26,1,READ,SELECT,TABLE,public.test,"SELECT
+  FROM test;",<none>
+--
+(0 rows)
+
+SELECT 1,
+	   current_user;
+NOTICE:  AUDIT: SESSION,27,1,READ,SELECT,,,"SELECT 1,
+	   current_user;",<none>
+ ?column? | current_user 
+----------+--------------
+        1 | super
+(1 row)
+
+DO $$
+DECLARE
+	test INT;
+BEGIN
+	SELECT 1
+	  INTO test;
+END $$;
+NOTICE:  AUDIT: SESSION,28,1,FUNCTION,DO,,,"DO $$
+DECLARE
+	test INT;
+BEGIN
+	SELECT 1
+	  INTO test;
+END $$;",<none>
+NOTICE:  AUDIT: SESSION,28,2,READ,SELECT,,,SELECT 1,<none>
+CONTEXT:  SQL statement "SELECT 1"
+PL/pgSQL function inline_code_block line 5 at SQL statement
+explain select 1;
+NOTICE:  AUDIT: SESSION,29,1,READ,SELECT,,,explain select 1;,<none>
+NOTICE:  AUDIT: SESSION,29,2,MISC,EXPLAIN,,,explain select 1;,<none>
+                QUERY PLAN                
+------------------------------------------
+ Result  (cost=0.00..0.01 rows=1 width=0)
+(1 row)
+
+--
+-- Test that looks inside of do blocks log
+INSERT INTO TEST (id)
+		  VALUES (1);
+NOTICE:  AUDIT: SESSION,30,1,WRITE,INSERT,TABLE,public.test,"INSERT INTO TEST (id)
+		  VALUES (1);",<none>
+INSERT INTO TEST (id)
+		  VALUES (2);
+NOTICE:  AUDIT: SESSION,31,1,WRITE,INSERT,TABLE,public.test,"INSERT INTO TEST (id)
+		  VALUES (2);",<none>
+INSERT INTO TEST (id)
+		  VALUES (3);
+NOTICE:  AUDIT: SESSION,32,1,WRITE,INSERT,TABLE,public.test,"INSERT INTO TEST (id)
+		  VALUES (3);",<none>
+DO $$
+DECLARE
+	result RECORD;
+BEGIN
+	FOR result IN
+		SELECT id
+		  FROM test
+	LOOP
+		INSERT INTO test (id)
+			 VALUES (result.id + 100);
+	END LOOP;
+END $$;
+NOTICE:  AUDIT: SESSION,33,1,FUNCTION,DO,,,"DO $$
+DECLARE
+	result RECORD;
+BEGIN
+	FOR result IN
+		SELECT id
+		  FROM test
+	LOOP
+		INSERT INTO test (id)
+			 VALUES (result.id + 100);
+	END LOOP;
+END $$;",<none>
+NOTICE:  AUDIT: SESSION,33,2,READ,SELECT,TABLE,public.test,"SELECT id
+		  FROM test",<none>
+CONTEXT:  PL/pgSQL function inline_code_block line 5 at FOR over SELECT rows
+NOTICE:  AUDIT: SESSION,33,3,WRITE,INSERT,TABLE,public.test,"INSERT INTO test (id)
+			 VALUES (result.id + 100)",",,"
+CONTEXT:  SQL statement "INSERT INTO test (id)
+			 VALUES (result.id + 100)"
+PL/pgSQL function inline_code_block line 9 at SQL statement
+NOTICE:  AUDIT: SESSION,33,4,WRITE,INSERT,TABLE,public.test,"INSERT INTO test (id)
+			 VALUES (result.id + 100)",",,"
+CONTEXT:  SQL statement "INSERT INTO test (id)
+			 VALUES (result.id + 100)"
+PL/pgSQL function inline_code_block line 9 at SQL statement
+NOTICE:  AUDIT: SESSION,33,5,WRITE,INSERT,TABLE,public.test,"INSERT INTO test (id)
+			 VALUES (result.id + 100)",",,"
+CONTEXT:  SQL statement "INSERT INTO test (id)
+			 VALUES (result.id + 100)"
+PL/pgSQL function inline_code_block line 9 at SQL statement
+--
+-- Test obfuscated dynamic sql for clean logging
+DO $$
+DECLARE
+	table_name TEXT = 'do_table';
+BEGIN
+	EXECUTE 'CREATE TABLE ' || table_name || ' ("weird name" INT)';
+	EXECUTE 'DROP table ' || table_name;
+END $$;
+NOTICE:  AUDIT: SESSION,34,1,FUNCTION,DO,,,"DO $$
+DECLARE
+	table_name TEXT = 'do_table';
+BEGIN
+	EXECUTE 'CREATE TABLE ' || table_name || ' (""weird name"" INT)';
+	EXECUTE 'DROP table ' || table_name;
+END $$;",<none>
+NOTICE:  AUDIT: SESSION,34,2,DDL,CREATE TABLE,TABLE,public.do_table,"CREATE TABLE do_table (""weird name"" INT)",<none>
+CONTEXT:  SQL statement "CREATE TABLE do_table ("weird name" INT)"
+PL/pgSQL function inline_code_block line 5 at EXECUTE statement
+NOTICE:  AUDIT: SESSION,34,3,DDL,DROP TABLE,TABLE,public.do_table,DROP table do_table,<none>
+CONTEXT:  SQL statement "DROP table do_table"
+PL/pgSQL function inline_code_block line 6 at EXECUTE statement
+--
+-- Generate an error and make sure the stack gets cleared
+DO $$
+BEGIN
+	CREATE TABLE bogus.test_block
+	(
+		id INT
+	);
+END $$;
+NOTICE:  AUDIT: SESSION,35,1,FUNCTION,DO,,,"DO $$
+BEGIN
+	CREATE TABLE bogus.test_block
+	(
+		id INT
+	);
+END $$;",<none>
+ERROR:  schema "bogus" does not exist
+LINE 1: CREATE TABLE bogus.test_block
+                     ^
+QUERY:  CREATE TABLE bogus.test_block
+	(
+		id INT
+	)
+CONTEXT:  PL/pgSQL function inline_code_block line 3 at SQL statement
+--
+-- Test alter table statements
+ALTER TABLE public.test
+	DROP COLUMN description ;
+NOTICE:  AUDIT: SESSION,36,1,DDL,ALTER TABLE,TABLE COLUMN,public.test.description,"ALTER TABLE public.test
+	DROP COLUMN description ;",<none>
+NOTICE:  AUDIT: SESSION,36,1,DDL,ALTER TABLE,TABLE,public.test,"ALTER TABLE public.test
+	DROP COLUMN description ;",<none>
+ALTER TABLE public.test
+	RENAME TO test2;
+NOTICE:  AUDIT: SESSION,37,1,DDL,ALTER TABLE,TABLE,public.test2,"ALTER TABLE public.test
+	RENAME TO test2;",<none>
+ALTER TABLE public.test2
+	SET SCHEMA test;
+NOTICE:  AUDIT: SESSION,38,1,DDL,ALTER TABLE,TABLE,test.test2,"ALTER TABLE public.test2
+	SET SCHEMA test;",<none>
+ALTER TABLE test.test2
+	ADD COLUMN description TEXT;
+NOTICE:  AUDIT: SESSION,39,1,DDL,ALTER TABLE,TABLE,test.test2,"ALTER TABLE test.test2
+	ADD COLUMN description TEXT;",<none>
+ALTER TABLE test.test2
+	DROP COLUMN description;
+NOTICE:  AUDIT: SESSION,40,1,DDL,ALTER TABLE,TABLE COLUMN,test.test2.description,"ALTER TABLE test.test2
+	DROP COLUMN description;",<none>
+NOTICE:  AUDIT: SESSION,40,1,DDL,ALTER TABLE,TABLE,test.test2,"ALTER TABLE test.test2
+	DROP COLUMN description;",<none>
+DROP TABLE test.test2;
+NOTICE:  AUDIT: SESSION,41,1,DDL,DROP TABLE,TABLE,test.test2,DROP TABLE test.test2;,<none>
+NOTICE:  AUDIT: SESSION,41,1,DDL,DROP TABLE,TABLE CONSTRAINT,test_pkey on test.test2,DROP TABLE test.test2;,<none>
+NOTICE:  AUDIT: SESSION,41,1,DDL,DROP TABLE,INDEX,test.test_pkey,DROP TABLE test.test2;,<none>
+--
+-- Test multiple statements with one semi-colon
+CREATE SCHEMA foo
+	CREATE TABLE foo.bar (id int)
+	CREATE TABLE foo.baz (id int);
+NOTICE:  AUDIT: SESSION,42,1,DDL,CREATE SCHEMA,SCHEMA,foo,"CREATE SCHEMA foo
+	CREATE TABLE foo.bar (id int)
+	CREATE TABLE foo.baz (id int);",<none>
+NOTICE:  AUDIT: SESSION,42,1,DDL,CREATE SCHEMA,TABLE,foo.bar,"CREATE SCHEMA foo
+	CREATE TABLE foo.bar (id int)
+	CREATE TABLE foo.baz (id int);",<none>
+NOTICE:  AUDIT: SESSION,42,1,DDL,CREATE SCHEMA,TABLE,foo.baz,"CREATE SCHEMA foo
+	CREATE TABLE foo.bar (id int)
+	CREATE TABLE foo.baz (id int);",<none>
+--
+-- Test aggregate
+CREATE FUNCTION public.int_add
+(
+	a INT,
+	b INT
+)
+	RETURNS INT LANGUAGE plpgsql AS $$
+BEGIN
+	return a + b;
+END $$;
+NOTICE:  AUDIT: SESSION,43,1,DDL,CREATE FUNCTION,FUNCTION,"public.int_add(integer,integer)","CREATE FUNCTION public.int_add
+(
+	a INT,
+	b INT
+)
+	RETURNS INT LANGUAGE plpgsql AS $$
+BEGIN
+	return a + b;
+END $$;",<none>
+SELECT int_add(1, 1);
+NOTICE:  AUDIT: SESSION,44,1,READ,SELECT,,,"SELECT int_add(1, 1);",<none>
+NOTICE:  AUDIT: SESSION,44,2,FUNCTION,EXECUTE,FUNCTION,public.int_add,"SELECT int_add(1, 1);",<none>
+ int_add 
+---------
+       2
+(1 row)
+
+CREATE AGGREGATE public.sum_test(INT) (SFUNC=public.int_add, STYPE=INT, INITCOND='0');
+NOTICE:  AUDIT: SESSION,45,1,DDL,CREATE AGGREGATE,AGGREGATE,public.sum_test(integer),"CREATE AGGREGATE public.sum_test(INT) (SFUNC=public.int_add, STYPE=INT, INITCOND='0');",<none>
+ALTER AGGREGATE public.sum_test(integer) RENAME TO sum_test2;
+NOTICE:  AUDIT: SESSION,46,1,DDL,ALTER AGGREGATE,AGGREGATE,public.sum_test2(integer),ALTER AGGREGATE public.sum_test(integer) RENAME TO sum_test2;,<none>
+--
+-- Test conversion
+CREATE CONVERSION public.conversion_test FOR 'SQL_ASCII' TO 'MULE_INTERNAL' FROM pg_catalog.ascii_to_mic;
+NOTICE:  AUDIT: SESSION,47,1,DDL,CREATE CONVERSION,CONVERSION,public.conversion_test,CREATE CONVERSION public.conversion_test FOR 'SQL_ASCII' TO 'MULE_INTERNAL' FROM pg_catalog.ascii_to_mic;,<none>
+ALTER CONVERSION public.conversion_test RENAME TO conversion_test2;
+NOTICE:  AUDIT: SESSION,48,1,DDL,ALTER CONVERSION,CONVERSION,public.conversion_test2,ALTER CONVERSION public.conversion_test RENAME TO conversion_test2;,<none>
+--
+-- Test create/alter/drop database
+CREATE DATABASE contrib_regression_pgaudit;
+NOTICE:  AUDIT: SESSION,49,1,DDL,CREATE DATABASE,,,CREATE DATABASE contrib_regression_pgaudit;,<none>
+ALTER DATABASE contrib_regression_pgaudit RENAME TO contrib_regression_pgaudit2;
+NOTICE:  AUDIT: SESSION,50,1,DDL,ALTER DATABASE,,,ALTER DATABASE contrib_regression_pgaudit RENAME TO contrib_regression_pgaudit2;,<none>
+DROP DATABASE contrib_regression_pgaudit2;
+NOTICE:  AUDIT: SESSION,51,1,DDL,DROP DATABASE,,,DROP DATABASE contrib_regression_pgaudit2;,<none>
+--
+-- Test that frees a memory context earlier than expected
+CREATE TABLE hoge
+(
+	id int
+);
+NOTICE:  AUDIT: SESSION,52,1,DDL,CREATE TABLE,TABLE,public.hoge,"CREATE TABLE hoge
+(
+	id int
+);",<none>
+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 ;
+NOTICE:  AUDIT: SESSION,53,1,DDL,CREATE FUNCTION,FUNCTION,public.test(),"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 ;",<none>
+SELECT test();
+NOTICE:  AUDIT: SESSION,54,1,READ,SELECT,,,SELECT test();,<none>
+NOTICE:  AUDIT: SESSION,54,2,FUNCTION,EXECUTE,FUNCTION,public.test,SELECT test();,<none>
+NOTICE:  AUDIT: SESSION,54,3,READ,SELECT,TABLE,public.hoge,select * from hoge,<none>
+CONTEXT:  PL/pgSQL function test() line 6 at OPEN
+ test 
+------
+     
+(1 row)
+
+--
+-- Delete all rows then delete 1 row
+SET pg_audit.log = 'write';
+SET pg_audit.role = 'auditor';
+create table bar
+(
+	col int
+);
+grant delete
+   on bar
+   to auditor;
+insert into bar (col)
+		 values (1);
+NOTICE:  AUDIT: SESSION,55,1,WRITE,INSERT,TABLE,public.bar,"insert into bar (col)
+		 values (1);",<none>
+delete from bar;
+NOTICE:  AUDIT: OBJECT,56,1,WRITE,DELETE,TABLE,public.bar,delete from bar;,<none>
+NOTICE:  AUDIT: SESSION,56,1,WRITE,DELETE,TABLE,public.bar,delete from bar;,<none>
+insert into bar (col)
+		 values (1);
+NOTICE:  AUDIT: SESSION,57,1,WRITE,INSERT,TABLE,public.bar,"insert into bar (col)
+		 values (1);",<none>
+delete from bar
+ where col = 1;
+NOTICE:  AUDIT: OBJECT,58,1,WRITE,DELETE,TABLE,public.bar,"delete from bar
+ where col = 1;",<none>
+NOTICE:  AUDIT: SESSION,58,1,WRITE,DELETE,TABLE,public.bar,"delete from bar
+ where col = 1;",<none>
+drop table bar;
+--
+-- Grant roles to each other
+SET pg_audit.log = 'role';
+GRANT user1 TO user2;
+NOTICE:  AUDIT: SESSION,59,1,ROLE,GRANT ROLE,,,GRANT user1 TO user2;,<none>
+REVOKE user1 FROM user2;
+NOTICE:  AUDIT: SESSION,60,1,ROLE,REVOKE ROLE,,,REVOKE user1 FROM user2;,<none>
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 0000000000000000000000000000000000000000..9d9ee83f73bb16a060a313c549976378eccc0970
--- /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 0000000000000000000000000000000000000000..384fc074814002300e3fdf0131a00b761135dad7
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.c
@@ -0,0 +1,1870 @@
+/*------------------------------------------------------------------------------
+ * pg_audit.c
+ *
+ * An audit logging extension for PostgreSQL. Provides detailed logging classes,
+ * object level logging, and fully-qualified object names for all DML and DDL
+ * statements where possible (See pgaudit.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);
+
+/* Prototypes for functions used with event triggers */
+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);
+
+/*
+ * Log Classes
+ *
+ * pgAudit categorizes actions into classes (eg: DDL, FUNCTION calls, READ
+ * queries, WRITE queries).  A GUC is provided for the administrator to
+ * configure which class (or classes) of actions to include in the
+ * audit log.  We track the currently active set of classes using
+ * auditLogBitmap.
+ */
+
+/* Bits within auditLogBitmap, defines the classes we understand */
+#define LOG_DDL			(1 << 0)	/* CREATE/DROP/ALTER objects */
+#define LOG_FUNCTION	(1 << 1)	/* Functions and DO blocks */
+#define LOG_MISC		(1 << 2)	/* Statements not covered */
+#define LOG_READ		(1 << 3)	/* SELECTs */
+#define LOG_ROLE		(1 << 4)	/* GRANT/REVOKE, CREATE/ALTER/DROP ROLE */
+#define LOG_WRITE		(1 << 5)	/* INSERT, UPDATE, DELETE, TRUNCATE */
+
+#define LOG_NONE		0			/* nothing */
+#define LOG_ALL			(0xFFFFFFFF)	/* All */
+
+/* GUC variable for pg_audit.log, which defines the classes to log. */
+char *auditLog = NULL;
+
+/* Bitmap of classes selected */
+static int auditLogBitmap = LOG_NONE;
+
+/*
+ * String constants for log classes - used when processing tokens in the
+ * pg_audit.log GUC.
+ */
+#define CLASS_DDL			"DDL"
+#define CLASS_FUNCTION		"FUNCTION"
+#define CLASS_MISC			"MISC"
+#define CLASS_READ			"READ"
+#define CLASS_ROLE			"ROLE"
+#define CLASS_WRITE			"WRITE"
+
+#define CLASS_NONE			"NONE"
+#define CLASS_ALL			"ALL"
+
+/*
+ * GUC variable for pg_audit.log_catalog
+ *
+ * Administrators can choose to NOT log queries when all relations used in
+ * the query are in pg_catalog.  Interactive sessions (eg: psql) can cause
+ * a lot of noise in the logs which might be uninteresting.
+ */
+bool auditLogCatalog = true;
+
+/*
+ * GUC variable for pg_audit.log_level
+ *
+ * Administrators can choose which log level the audit log is to be logged
+ * at.  The default level is LOG, which goes into the server log but does
+ * not go to the client.  Set to NOTICE in the regression tests.
+ */
+char *auditLogLevelString = NULL;
+int auditLogLevel = LOG;
+
+/*
+ * GUC variable for pg_audit.log_parameter
+ *
+ * Administrators can choose if parameters passed into a statement are
+ * included in the audit log.
+ */
+bool auditLogParameter = false;
+
+/*
+ * GUC variable for pg_audit.log_relation
+ *
+ * Administrators can choose, in SESSION logging, to log each relation involved
+ * in READ/WRITE class queries.  By default, SESSION logs include the query but
+ * do not have a log entry for each relation.
+ */
+bool auditLogRelation = false;
+
+/*
+ * GUC variable for pg_audit.log_statement_once
+ *
+ * Administrators can choose to have the statement run logged only once instead
+ * of on every line.  By default, the statement is repeated on every line of
+ * the audit log to facilitate searching, but this can cause the log to be
+ * unnecessairly bloated in some environments.
+ */
+bool auditLogStatementOnce = false;
+
+/*
+ * GUC variable for pg_audit.role
+ *
+ * Administrators can choose which role to base OBJECT auditing off of.
+ * Object-level auditing uses the privileges which are granted to this role to
+ * determine if a statement should be logged.
+ */
+char *auditRole = NULL;
+
+/*
+ * String constants for the audit log fields.
+ */
+
+/*
+ * Audit type, which is responsbile for the log message
+ */
+#define AUDIT_TYPE_OBJECT	"OBJECT"
+#define AUDIT_TYPE_SESSION	"SESSION"
+
+/*
+ * Command, used for SELECT/DML and function calls.
+ *
+ * We hook into the executor, but we do not have access to the parsetree there.
+ * Therefore we can't simply call CreateCommandTag() to get the command and have
+ * to build it ourselves based on what information we do have.
+ *
+ * These should be updated if new commands are added to what the exectuor
+ * currently handles.  Note that most of the interesting commands do not go
+ * through the executor but rather ProcessUtility, where we have the parsetree.
+ */
+#define COMMAND_SELECT				"SELECT"
+#define COMMAND_INSERT				"INSERT"
+#define COMMAND_UPDATE				"UPDATE"
+#define COMMAND_DELETE				"DELETE"
+#define COMMAND_EXECUTE				"EXECUTE"
+#define COMMAND_UNKNOWN				"UNKNOWN"
+
+/*
+ * Object type, used for SELECT/DML statements and function calls.
+ *
+ * For relation objects, this is essentially relkind (though we do not have
+ * access to a function which will just return a string given a relkind;
+ * getRelationTypeDescription() comes close but is not public currently).
+ *
+ * We also handle functions, so it isn't quite as simple as just relkind.
+ *
+ * This should be kept consistent with what is returned from
+ * pg_event_trigger_ddl_commands(), as that's what we use for DDL.
+ */
+#define OBJECT_TYPE_TABLE			"TABLE"
+#define OBJECT_TYPE_INDEX			"INDEX"
+#define OBJECT_TYPE_SEQUENCE		"SEQUENCE"
+#define OBJECT_TYPE_TOASTVALUE		"TOAST TABLE"
+#define OBJECT_TYPE_VIEW			"VIEW"
+#define OBJECT_TYPE_MATVIEW			"MATERIALIZED VIEW"
+#define OBJECT_TYPE_COMPOSITE_TYPE	"COMPOSITE TYPE"
+#define OBJECT_TYPE_FOREIGN_TABLE	"FOREIGN TABLE"
+#define OBJECT_TYPE_FUNCTION		"FUNCTION"
+
+#define OBJECT_TYPE_UNKNOWN			"UNKNOWN"
+
+/*
+ * String constants for testing role commands.  Rename and drop role statements
+ * are assigned the nodeTag T_RenameStmt and T_DropStmt respectively.  This is
+ * not very useful for classification, so we resort to comparing strings
+ * against the result of CreateCommandTag(parsetree).
+ */
+#define COMMAND_ALTER_ROLE			"ALTER ROLE"
+#define COMMAND_DROP_ROLE			"DROP ROLE"
+
+/*
+ * An AuditEvent represents an operation that potentially affects a single
+ * object.  If a statement affects multiple objects then multiple AuditEvents
+ * are created to represent them.
+ */
+typedef struct
+{
+	int64 statementId;			/* Simple counter */
+	int64 substatementId;		/* Simple counter */
+
+	LogStmtLevel logStmtLevel;	/* From GetCommandLogLevel when possible, */
+								/* generated when not. */
+	NodeTag commandTag;			/* same here */
+	const char *command;		/* same here */
+	const char *objectType;		/* From event trigger when possible */
+								/* generated when not. */
+	char *objectName;			/* Fully qualified object identification */
+	const char *commandText;	/* sourceText / queryString */
+	ParamListInfo paramList;	/* QueryDesc/ProcessUtility parameters */
+
+	bool granted;				/* Audit role has object permissions? */
+	bool logged;				/* Track if we have logged this event, used */
+								/* post-ProcessUtility to make sure we log */
+	bool statementLogged;		/* Track if we have logged the statement */
+} AuditEvent;
+
+/*
+ * A simple FIFO queue to keep track of the current stack of audit events.
+ */
+typedef struct AuditEventStackItem
+{
+	struct AuditEventStackItem *next;
+
+	AuditEvent auditEvent;
+
+	int64 stackId;
+
+	MemoryContext contextAudit;
+	MemoryContextCallback contextCallback;
+} AuditEventStackItem;
+
+AuditEventStackItem *auditEventStack = NULL;
+
+/*
+ * pgAudit runs queries of its own when using the event trigger system.
+ *
+ * Track when we are running a query and don't log it.
+ */
+static bool internalStatement = false;
+
+/*
+ * Track running total for statements and substatements and whether or not
+ * anything has been logged since the current statement began.
+ */
+static int64 statementTotal = 0;
+static int64 substatementTotal = 0;
+static int64 stackTotal = 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 to the item after the freed item */
+			auditEventStack = nextItem->next;
+
+			/* If the stack is not empty */
+			if (auditEventStack == NULL)
+			{
+				/*
+				 * Reset internal statement to false.  Normally this will be
+				 * reset but in case of an error it might be left set.
+				 */
+				internalStatement = false;
+
+				/*
+				 * Reset sub statement total so the next statement will start
+				 * from 1.
+				 */
+				substatementTotal = 0;
+
+				/*
+				 * Reset statement logged so that next statement will be logged.
+				 */
+				statementLogged = false;
+			}
+
+			return;
+		}
+
+		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 to contain the stack item.  This will be
+	 * free'd on stack_pop, or by our callback when the parent context is
+	 * destroyed.
+	 */
+	contextAudit = AllocSetContextCreate(CurrentMemoryContext,
+										 "pg_audit stack context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* Save the old context to switch back to at the end */
+	contextOld = MemoryContextSwitchTo(contextAudit);
+
+	/* Create our new stack item in our context */
+	stackItem = palloc0(sizeof(AuditEventStackItem));
+	stackItem->contextAudit = contextAudit;
+	stackItem->stackId = ++stackTotal;
+
+	/*
+	 * 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 new item onto the stack */
+	if (auditEventStack != NULL)
+		stackItem->next = auditEventStack;
+	else
+		stackItem->next = NULL;
+
+	auditEventStack = stackItem;
+
+	MemoryContextSwitchTo(contextOld);
+
+	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(int64 stackId)
+{
+	/* Make sure what we want to delete is at the top of the stack */
+	if (auditEventStack != NULL && auditEventStack->stackId == stackId)
+		MemoryContextDelete(auditEventStack->contextAudit);
+	else
+		elog(ERROR, "pg_audit stack item %ld not found on top - cannot pop",
+					stackId);
+}
+
+/*
+ * Check that an item is on the stack.  If not, an error will be raised since
+ * this is a bad state to be in and it might mean audit records are being lost.
+ */
+static void
+stack_valid(int64 stackId)
+{
+	AuditEventStackItem *nextItem = auditEventStack;
+
+	/* Look through the stack for the stack entry */
+	while (nextItem != NULL && nextItem->stackId != stackId)
+		nextItem = nextItem->next;
+
+	/* If we didn't find it, something went wrong. */
+	if (nextItem == NULL)
+		elog(ERROR, "pg_audit stack item %ld not found - top of stack is %ld",
+			 stackId, auditEventStack == NULL ? -1 : auditEventStack->stackId);
+
+	return;
+}
+
+/*
+ * 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 do nothing.  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, classifies it, then logs it if appropriate.
+ *
+ * Logging is decided based on if the statement is in one of the classes being
+ * logged or if an object used has been marked for auditing.
+ *
+ * Objects are marked for auditing by the auditor role being granted access
+ * to the object.  The kind of access (INSERT, UPDATE, etc) is also considered
+ * and logging is only performed when the kind of access matches the granted
+ * right on the object.
+ *
+ * This will need to be updated if new kinds of GRANTs are added.
+ */
+static void
+log_audit_event(AuditEventStackItem *stackItem)
+{
+	/* By default, put everything in the MISC class. */
+	int				class = LOG_MISC;
+	const char	   *className = CLASS_MISC;
+	MemoryContext	contextOld;
+	StringInfoData	auditStr;
+
+
+	/* Classify the statement using log stmt level and the command tag */
+	switch (stackItem->auditEvent.logStmtLevel)
+	{
+		/* All mods go in WRITE class, execpt EXECUTE */
+		case LOGSTMT_MOD:
+			className = CLASS_WRITE;
+			class = LOG_WRITE;
+
+			switch (stackItem->auditEvent.commandTag)
+			{
+				/* Currently, only EXECUTE is different */
+				case T_ExecuteStmt:
+					className = CLASS_MISC;
+					class = LOG_MISC;
+					break;
+				default:
+					break;
+			}
+			break;
+
+		/* These are DDL, unless they are ROLE */
+		case LOGSTMT_DDL:
+			className = CLASS_DDL;
+			class = LOG_DDL;
+
+			/* Identify role statements */
+			switch (stackItem->auditEvent.commandTag)
+			{
+				/* We know these are all role statements */
+				case T_GrantStmt:
+				case T_GrantRoleStmt:
+				case T_CreateRoleStmt:
+				case T_DropRoleStmt:
+				case T_AlterRoleStmt:
+				case T_AlterRoleSetStmt:
+					className = CLASS_ROLE;
+					class = LOG_ROLE;
+					break;
+				/*
+				 * Rename and Drop are general and therefore we have to do an
+				 * additional check against the command string to see if they
+				 * are role or regular DDL.
+				 */
+				case T_RenameStmt:
+				case T_DropStmt:
+					if (pg_strcasecmp(stackItem->auditEvent.command,
+									  COMMAND_ALTER_ROLE) == 0 ||
+						pg_strcasecmp(stackItem->auditEvent.command,
+									  COMMAND_DROP_ROLE) == 0)
+					{
+						className = CLASS_ROLE;
+						class = LOG_ROLE;
+					}
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		/* Classify the rest */
+		case LOGSTMT_ALL:
+			switch (stackItem->auditEvent.commandTag)
+			{
+				/* READ statements */
+				case T_CopyStmt:
+				case T_SelectStmt:
+				case T_PrepareStmt:
+				case T_PlannedStmt:
+					className = CLASS_READ;
+					class = LOG_READ;
+					break;
+
+				/* Reindex is DDL (because cluster is DDL) */
+				case T_ReindexStmt:
+					className = CLASS_DDL;
+					class = LOG_DDL;
+					break;
+
+				/* FUNCTION statements */
+				case T_DoStmt:
+					className = CLASS_FUNCTION;
+					class = LOG_FUNCTION;
+					break;
+
+				default:
+					break;
+			}
+			break;
+
+		case LOGSTMT_NONE:
+			break;
+	}
+
+	/*
+	 * Only log the statement if:
+	 *
+	 * 1. If object was selected for audit logging (granted)
+	 * 2. The statement belongs to a class that is being logged
+	 *
+	 * If neither of these is true, return.
+	 */
+	if (!stackItem->auditEvent.granted && !(auditLogBitmap & class))
+		return;
+
+	/*
+	 * Use audit memory context in case something is not free'd while
+	 * appending strings and parameters.
+	 */
+	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 substring
+	 *
+	 * The type-of-audit-log and statement/substatement ID are handled below,
+	 * this string is everything else.
+	 */
+	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);
+
+	/*
+	 * If auditLogStatmentOnce is true, then only log the statement and
+	 * parameters if they have not already been logged for this substatement.
+	 */
+	appendStringInfoCharMacro(&auditStr, ',');
+	if (!stackItem->auditEvent.statementLogged || !auditLogStatementOnce)
+	{
+		append_valid_csv(&auditStr, stackItem->auditEvent.commandText);
+
+		appendStringInfoCharMacro(&auditStr, ',');
+
+		/* Handle parameter logging, if enabled. */
+		if (auditLogParameter)
+		{
+			int				paramIdx;
+			int				numParams;
+			StringInfoData	paramStrResult;
+			ParamListInfo	paramList = stackItem->auditEvent.paramList;
+
+			numParams = paramList == NULL ? 0 : paramList->numParams;
+
+			/* Create the param substring */
+			initStringInfo(&paramStrResult);
+
+			/* Iterate through all params */
+			for (paramIdx = 0; paramList != NULL && paramIdx < numParams;
+				 paramIdx++)
+			{
+				ParamExternData *prm = &paramList->params[paramIdx];
+				Oid 			 typeOutput;
+				bool 			 typeIsVarLena;
+				char 			*paramStr;
+
+				/* Add a comma for each param */
+				if (paramIdx != 0)
+					appendStringInfoCharMacro(&paramStrResult, ',');
+
+				/* Skip 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(&paramStrResult, paramStr);
+				pfree(paramStr);
+			}
+
+			if (numParams == 0)
+				appendStringInfoString(&auditStr, "<none>");
+			else
+				append_valid_csv(&auditStr, paramStrResult.data);
+		}
+		else
+			appendStringInfoString(&auditStr, "<not logged>");
+
+		stackItem->auditEvent.statementLogged = true;
+	}
+	else
+		/* we were asked to not log it */
+		appendStringInfoString(&auditStr,
+				"<previously logged>,<previously logged>");
+
+	/* Log the audit entry */
+	elog(auditLogLevel, "AUDIT: %s,%ld,%ld,%s,%s",
+			stackItem->auditEvent.granted ?
+				AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,
+			stackItem->auditEvent.statementId,
+			stackItem->auditEvent.substatementId,
+			className, auditStr.data);
+
+			stackItem->auditEvent.logged = true;
+
+	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
+audit_on_acl(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 number of items */
+	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
+audit_on_relation(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));
+	if (!HeapTupleIsValid(tuple))
+		return false;
+
+	/* Get the relation's ACL */
+	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
+							   &isNull);
+
+	/* Only check if non-NULL, since NULL means no permissions */
+	if (!isNull)
+		result = audit_on_acl(aclDatum, auditOid, mask);
+
+	/* Free the relation tuple */
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a column.
+ */
+static bool
+audit_on_attribute(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));
+	if (!HeapTupleIsValid(attTuple))
+		return false;
+
+	/* Only consider attributes that have not been dropped */
+	if (!((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped)
+	{
+		aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+								   &isNull);
+
+		if (!isNull)
+			result = audit_on_acl(aclDatum, auditOid, mask);
+	}
+
+	/* Free attribute */
+	ReleaseSysCache(attTuple);
+
+	return result;
+}
+
+/*
+ * Check if a role has any of the permissions in the mask on a column in
+ * the provided set.  If the set is empty, then all valid columns in the
+ * relation will be tested.
+ */
+static bool
+audit_on_any_attribute(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 columns */
+		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 (audit_on_attribute(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 &&
+			audit_on_attribute(relOid, col, auditOid, mode))
+		{
+			result = true;
+			break;
+		}
+	}
+
+	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;
+
+		/*
+		 * If we are not logging all-catalog queries (auditLogCatalog is false)
+		 * then filter out any system relations here.
+		 */
+		relOid = rte->relid;
+		rel = relation_open(relOid, NoLock);
+
+		if (!auditLogCatalog && IsSystemNamespace(RelationGetNamespace(rel)))
+		{
+			relation_close(rel, NoLock);
+			continue;
+		}
+
+		/*
+		 * Default is that this was not through a grant, to support session
+		 * logging.  Will be updated below if a grant is found.
+		 */
+		auditEventStack->auditEvent.granted = false;
+
+		/*
+		 * If this is the first RTE then session log unless auditLogRelation
+		 * is set.
+		 */
+		if (first && !auditLogRelation)
+		{
+			log_audit_event(auditEventStack);
+
+			first = false;
+		}
+
+		/*
+		 * 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;
+		}
+
+		/* Use the relation type to assign object 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 a copy of the relation name and assign it to object 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 | ACL_DELETE) &
+				rte->requiredPerms;
+
+			/*
+			 * If any of the required permissions for the relation are granted
+			 * to the audit role then audit the relation
+			 */
+			if (audit_on_relation(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
+				 */
+				if (auditPerms & ACL_SELECT)
+					auditEventStack->auditEvent.granted =
+						audit_on_any_attribute(relOid, auditOid,
+											   rte->selectedCols,
+											   ACL_SELECT);
+
+				/*
+				 * Check the insert columns
+				 */
+				if (!auditEventStack->auditEvent.granted &&
+					auditPerms & ACL_INSERT)
+					auditEventStack->auditEvent.granted =
+						audit_on_any_attribute(relOid, auditOid,
+											   rte->insertedCols,
+											   auditPerms);
+
+				/*
+				 * Check the update columns
+				 */
+				if (!auditEventStack->auditEvent.granted &&
+					auditPerms & ACL_UPDATE)
+					auditEventStack->auditEvent.granted =
+						audit_on_any_attribute(relOid, auditOid,
+											   rte->updatedCols,
+											   auditPerms);
+			}
+		}
+
+		/* Do relation level logging if a grant was found */
+		if (auditEventStack->auditEvent.granted)
+		{
+			auditEventStack->auditEvent.logged = false;
+			log_audit_event(auditEventStack);
+		}
+
+		/* Do relation level logging if auditLogRelation is set */
+		if (auditLogRelation)
+		{
+			auditEventStack->auditEvent.logged = false;
+			auditEventStack->auditEvent.granted = 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 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(stackItem->stackId);
+}
+
+/*
+ * 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;
+
+/*
+ * Hook ExecutorStart to get the query text and basic command type for queries
+ * that do not contain a table and so can't be idenitified accurately in
+ * ExecutorCheckPerms.
+ */
+static void
+pg_audit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
+{
+	AuditEventStackItem *stackItem = NULL;
+
+	if (!internalStatement)
+	{
+		/* Push the audit even onto the stack */
+		stackItem = stack_push();
+
+		/* Initialize command using queryDesc->operation */
+		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.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);
+
+	/*
+	 * Move the stack memory context to the query memory context.  This needs to
+	 * be done here because the query context does not exist before the call
+	 * to standard_ExecutorStart() but the stack item is required by
+	 * pg_audit_ExecutorCheckPerms_hook() which is called during
+	 * standard_ExecutorStart().
+	 */
+	if (stackItem)
+		MemoryContextSetParent(stackItem->contextAudit,
+							   queryDesc->estate->es_query_cxt);
+}
+
+/*
+ * Hook ExecutorCheckPerms to do session and object auditing for DML.
+ */
+static bool
+pg_audit_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 ProcessUtility to do session auditing for DDL and utility commands.
+ */
+static void
+pg_audit_ProcessUtility_hook(Node *parsetree,
+							 const char *queryString,
+							 ProcessUtilityContext context,
+							 ParamListInfo params,
+							 DestReceiver *dest,
+							 char *completionTag)
+{
+	AuditEventStackItem *stackItem = NULL;
+	int64 stackId;
+
+	/*
+	 * Don't audit substatements.  All the substatements we care about should
+	 * be covered by the event triggers.
+	 */
+	if (context <= PROCESS_UTILITY_QUERY && !IsAbortedTransactionBlockState())
+	{
+		/* Process top level utility statement */
+		if (context == PROCESS_UTILITY_TOPLEVEL)
+		{
+			if (auditEventStack != NULL)
+				elog(ERROR, "pg_audit stack is not empty");
+
+			stackItem = stack_push();
+			stackItem->auditEvent.paramList = params;
+		}
+		else
+			stackItem = stack_push();
+
+		stackId = stackItem->stackId;
+		stackItem->auditEvent.logStmtLevel = GetCommandLogLevel(parsetree);
+		stackItem->auditEvent.commandTag = nodeTag(parsetree);
+		stackItem->auditEvent.command = CreateCommandTag(parsetree);
+		stackItem->auditEvent.commandText = queryString;
+
+		/*
+		 * If this is a DO block log it before calling the next ProcessUtility
+		 * hook.
+		 */
+		if (auditLogBitmap & LOG_FUNCTION &&
+			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.  Also check that this event was
+	 * not popped off the stack by a memory context being free'd elsewhere.
+	 */
+	if (stackItem && !IsAbortedTransactionBlockState())
+	{
+		/*
+		 * Make sure the item we want to log is still on the stack - if not then
+		 * something has gone wrong and an error will be raised.
+		 */
+		stack_valid(stackId);
+
+		/* 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)
+			log_audit_event(stackItem);
+	}
+}
+
+/*
+ * Hook object_access_hook to provide fully-qualified object names for function
+ * calls.
+ */
+static void
+pg_audit_object_access_hook(ObjectAccessType access,
+							Oid classId,
+							Oid objectId,
+							int subId,
+							void *arg)
+{
+	if (auditLogBitmap & LOG_FUNCTION && access == OAT_FUNCTION_EXECUTE &&
+		auditEventStack && !IsAbortedTransactionBlockState())
+		log_function_execute(objectId);
+
+	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.
+ *
+ * Drop statements are handled below through the older sql_drop event trigger.
+ */
+Datum
+pg_audit_ddl_command_end(PG_FUNCTION_ARGS)
+{
+	EventTriggerData *eventData;
+	int				  result, row;
+	TupleDesc		  spiTupDesc;
+	const char		 *query;
+	MemoryContext 	  contextQuery;
+	MemoryContext 	  contextOld;
+
+	/* Continue only if session DDL logging is enabled */
+	if (~auditLogBitmap & LOG_DDL)
+		PG_RETURN_NULL();
+
+	/* Be sure the module was loaded */
+	if (!auditEventStack)
+		elog(ERROR, "pg_audit not loaded before call to "
+					"pg_audit_ddl_command_end()");
+
+	/* 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 for query */
+	contextQuery = AllocSetContextCreate(
+					CurrentMemoryContext,
+					"pg_audit_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;
+
+	auditEventStack->auditEvent.logStmtLevel =
+		GetCommandLogLevel(eventData->parsetree);
+	auditEventStack->auditEvent.commandTag =
+		nodeTag(eventData->parsetree);
+	auditEventStack->auditEvent.command =
+		CreateCommandTag(eventData->parsetree);
+
+	/* Return objects affected by the (non drop) DDL statement */
+	query = "SELECT UPPER(object_type), object_identity\n"
+			"  FROM pg_event_trigger_ddl_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;
+
+		spiTuple = SPI_tuptable->vals[row];
+
+		/* Supply object name and type for audit event */
+		auditEventStack->auditEvent.objectType =
+			SPI_getvalue(spiTuple, spiTupDesc, 1);
+		auditEventStack->auditEvent.objectName =
+			SPI_getvalue(spiTuple, spiTupDesc, 2);
+
+		/* Log the audit event */
+		log_audit_event(auditEventStack);
+	}
+
+	/* Complete the query */
+	SPI_finish();
+
+	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)
+{
+	int				  result, row;
+	TupleDesc		  spiTupDesc;
+	const char		 *query;
+	MemoryContext 	  contextQuery;
+	MemoryContext 	  contextOld;
+
+	if (~auditLogBitmap & LOG_DDL)
+		PG_RETURN_NULL();
+
+	/* Be sure the module was loaded */
+	if (!auditEventStack)
+		elog(ERROR, "pg_audit not loaded before call to "
+					"pg_audit_sql_drop()");
+
+	/* 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 for the query */
+	contextQuery = AllocSetContextCreate(
+					CurrentMemoryContext,
+					"pg_audit_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 UPPER(object_type),\n"
+			"       object_identity\n"
+			"  FROM pg_event_trigger_dropped_objects()\n"
+			" WHERE lower(object_type) <> 'type'\n"
+			"   AND schema_name <> 'pg_toast'";
+
+	/* 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;
+
+		spiTuple = SPI_tuptable->vals[row];
+
+		auditEventStack->auditEvent.objectType =
+			SPI_getvalue(spiTuple, spiTupDesc, 1);
+		auditEventStack->auditEvent.objectName =
+				SPI_getvalue(spiTuple, spiTupDesc, 2);
+
+		log_audit_event(auditEventStack);
+	}
+
+	/* Complete the query */
+	SPI_finish();
+
+	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_pg_audit_log(char **newVal, void **extra, GucSource source)
+{
+	List *flagRawList;
+	char *rawVal;
+	ListCell *lt;
+	int *flags;
+
+	/* Make sure newval is a comma-separated list of tokens. */
+	rawVal = pstrdup(*newVal);
+	if (!SplitIdentifierString(rawVal, ',', &flagRawList))
+	{
+		GUC_check_errdetail("List syntax is invalid");
+		list_free(flagRawList);
+		pfree(rawVal);
+		return false;
+	}
+
+	/*
+	 * Check that we recognise each token, and add it to the bitmap we're
+	 * building up in a newly-allocated int *f.
+	 */
+	if (!(flags = (int *)malloc(sizeof(int))))
+		return false;
+
+	*flags = 0;
+
+	foreach(lt, flagRawList)
+	{
+		bool subtract = false;
+		int class;
+
+		/* Retrieve a token */
+		char *token = (char *)lfirst(lt);
+
+		/* If token is preceded by -, then the 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_ROLE) == 0)
+			class = LOG_ROLE;
+		else if (pg_strcasecmp(token, CLASS_WRITE) == 0)
+			class = LOG_WRITE;
+		else
+		{
+			free(flags);
+			pfree(rawVal);
+			list_free(flagRawList);
+			return false;
+		}
+
+		/* Add or subtract class bits from the log bitmap */
+		if (subtract)
+			*flags &= ~class;
+		else
+			*flags |= class;
+	}
+
+	pfree(rawVal);
+	list_free(flagRawList);
+
+	/* Store the bitmap for assign_pg_audit_log */
+	*extra = flags;
+
+	return true;
+}
+
+/*
+ * Set pg_audit_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_pg_audit_log(const char *newVal, void *extra)
+{
+	if (extra)
+		auditLogBitmap = *(int *)extra;
+}
+
+/*
+ * Take a pg_audit.log_level value such as "debug" and check that is is valid.
+ * Return the enum value so it does not have to be checked again in the assign
+ * function.
+ */
+static bool
+check_pg_audit_log_level(char **newVal, void **extra, GucSource source)
+{
+	int *logLevel;
+
+	/* Allocate memory to store the log level */
+	if (!(logLevel = (int *)malloc(sizeof(int))))
+		return false;
+
+	/* Find the log level enum */
+	if (pg_strcasecmp(*newVal, "debug") == 0)
+		*logLevel = DEBUG2;
+	else if (pg_strcasecmp(*newVal, "debug5") == 0)
+		*logLevel = DEBUG5;
+	else if (pg_strcasecmp(*newVal, "debug4") == 0)
+		*logLevel = DEBUG4;
+	else if (pg_strcasecmp(*newVal, "debug3") == 0)
+		*logLevel = DEBUG3;
+	else if (pg_strcasecmp(*newVal, "debug2") == 0)
+		*logLevel = DEBUG2;
+	else if (pg_strcasecmp(*newVal, "debug1") == 0)
+		*logLevel = DEBUG1;
+	else if (pg_strcasecmp(*newVal, "info") == 0)
+		*logLevel = INFO;
+	else if (pg_strcasecmp(*newVal, "notice") == 0)
+		*logLevel = NOTICE;
+	else if (pg_strcasecmp(*newVal, "warning") == 0)
+		*logLevel = WARNING;
+	else if (pg_strcasecmp(*newVal, "error") == 0)
+		*logLevel = ERROR;
+	else if (pg_strcasecmp(*newVal, "log") == 0)
+		*logLevel = LOG;
+	else if (pg_strcasecmp(*newVal, "fatal") == 0)
+		*logLevel = FATAL;
+	else if (pg_strcasecmp(*newVal, "panic") == 0)
+		*logLevel = PANIC;
+
+	/* Error if the log level enum is not found */
+	else
+	{
+		free(logLevel);
+		return false;
+	}
+
+	/* Return the log level enum */
+	*extra = logLevel;
+
+	return true;
+}
+
+/*
+ * Set pg_audit_log from extra (ignoring newVal, which has already been
+ * converted to an enum above). Note that extra may not be set if the
+ * assignment is to be suppressed.
+ */
+static void
+assign_pg_audit_log_level(const char *newVal, void *extra)
+{
+	if (extra)
+		auditLogLevel = *(int *)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")));
+
+	/* Define pg_audit.log */
+	DefineCustomStringVariable(
+		"pg_audit.log",
+
+		"Specifies which classes of statements will be logged by session audit "
+		"logging. Multiple classes can be provided using a comma-separated "
+		"list and classes can be subtracted by prefacing the class with a "
+		"- sign.",
+
+		NULL,
+		&auditLog,
+		"none",
+		PGC_SUSET,
+		GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+		check_pg_audit_log,
+		assign_pg_audit_log,
+		NULL);
+
+	/* Define pg_audit.log_catalog */
+	DefineCustomBoolVariable(
+		"pg_audit.log_catalog",
+
+		"Specifies that session logging should be enabled in the case where "
+		"all relations in a statement are in pg_catalog.  Disabling this "
+		"setting will reduce noise in the log from tools like psql and PgAdmin "
+		"that query the catalog heavily.",
+
+		NULL,
+		&auditLogCatalog,
+		true,
+		PGC_SUSET,
+		GUC_NOT_IN_SAMPLE,
+		NULL, NULL, NULL);
+
+	/* Define pg_audit.log_level */
+	DefineCustomStringVariable(
+		"pg_audit.log_level",
+
+		"Specifies the log level that will be used for log entries. This "
+		"setting is used for regression testing and may also be useful to end "
+		"users for testing or other purposes.  It is not intended to be used "
+		"in a production environment as it may leak which statements are being "
+		"logged to the user.",
+
+		NULL,
+		&auditLogLevelString,
+		"log",
+		PGC_SUSET,
+		GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,
+		check_pg_audit_log_level,
+		assign_pg_audit_log_level,
+		NULL);
+
+	/* Define pg_audit.log_parameter */
+	DefineCustomBoolVariable(
+		"pg_audit.log_parameter",
+
+		"Specifies that audit logging should include the parameters that were "
+		"passed with the statement. When parameters are present they will be "
+		"be included in CSV format after the statement text.",
+
+		NULL,
+		&auditLogParameter,
+		false,
+		PGC_SUSET,
+		GUC_NOT_IN_SAMPLE,
+		NULL, NULL, NULL);
+
+	/* Define pg_audit.log_relation */
+	DefineCustomBoolVariable(
+		"pg_audit.log_relation",
+
+		"Specifies whether session audit logging should create a separate log "
+		"entry for each relation referenced in a SELECT or DML statement. "
+		"This is a useful shortcut for exhaustive logging without using object "
+		"audit logging.",
+
+		NULL,
+		&auditLogRelation,
+		false,
+		PGC_SUSET,
+		GUC_NOT_IN_SAMPLE,
+		NULL, NULL, NULL);
+
+	/* Define pg_audit.log_statement_once */
+	DefineCustomBoolVariable(
+		"pg_audit.log_statement_once",
+
+		"Specifies whether logging will include the statement text and "
+		"parameters with the first log entry for a statement/substatement "
+		"combination or with every entry.  Disabling this setting will result "
+		"in less verbose logging but may make it more difficult to determine "
+		"the statement that generated a log entry, though the "
+		"statement/substatement pair along with the process id should suffice "
+		"to identify the statement text logged with a previous entry.",
+
+		NULL,
+		&auditLogStatementOnce,
+		false,
+		PGC_SUSET,
+		GUC_NOT_IN_SAMPLE,
+		NULL, NULL, NULL);
+
+	/* Define pg_audit.role */
+	DefineCustomStringVariable(
+		"pg_audit.role",
+
+		"Specifies the master role to use for object audit logging.  Muliple "
+		"audit roles can be defined by granting them to the master role. This "
+		"allows multiple groups to be in charge of different aspects of audit "
+		"logging.",
+
+		NULL,
+		&auditRole,
+		"",
+		PGC_SUSET,
+		GUC_NOT_IN_SAMPLE,
+		NULL, NULL, NULL);
+
+	/*
+	 * Install our hook functions after saving the existing pointers to preserve
+	 * the chains.
+	 */
+	next_ExecutorStart_hook = ExecutorStart_hook;
+	ExecutorStart_hook = pg_audit_ExecutorStart_hook;
+
+	next_ExecutorCheckPerms_hook = ExecutorCheckPerms_hook;
+	ExecutorCheckPerms_hook = pg_audit_ExecutorCheckPerms_hook;
+
+	next_ProcessUtility_hook = ProcessUtility_hook;
+	ProcessUtility_hook = pg_audit_ProcessUtility_hook;
+
+	next_object_access_hook = object_access_hook;
+	object_access_hook = pg_audit_object_access_hook;
+}
diff --git a/contrib/pg_audit/pg_audit.conf b/contrib/pg_audit/pg_audit.conf
new file mode 100644
index 0000000000000000000000000000000000000000..e9f4a2204f69c4ddfe28e4be579263e37c9bff9a
--- /dev/null
+++ b/contrib/pg_audit/pg_audit.conf
@@ -0,0 +1 @@
+shared_preload_libraries = pg_audit
diff --git a/contrib/pg_audit/pg_audit.control b/contrib/pg_audit/pg_audit.control
new file mode 100644
index 0000000000000000000000000000000000000000..6730c689167d59e1ac02f40e9067db7452161820
--- /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/sql/pg_audit.sql b/contrib/pg_audit/sql/pg_audit.sql
new file mode 100644
index 0000000000000000000000000000000000000000..2a63674c5715604efd34b4293e8e960ba24107a7
--- /dev/null
+++ b/contrib/pg_audit/sql/pg_audit.sql
@@ -0,0 +1,617 @@
+-- Load pg_audit module
+create extension pg_audit;
+
+--
+-- Audit log fields are:
+--     AUDIT_TYPE - SESSION or OBJECT
+--     STATEMENT_ID - ID of the statement in the current backend
+--     SUBSTATEMENT_ID - ID of the substatement in the current backend
+--     CLASS - Class of statement being logged (e.g. ROLE, READ, WRITE)
+--     COMMAND - e.g. SELECT, CREATE ROLE, UPDATE
+--     OBJECT_TYPE - When available, type of object acted on (e.g. TABLE, VIEW)
+--     OBJECT_NAME - When available, fully-qualified table of object
+--     STATEMENT - The statement being logged
+--     PARAMETER - If parameter logging is requested, they will follow the
+--                 statement
+
+--
+-- Create a superuser role that we know the name of for testing
+CREATE USER super SUPERUSER;
+ALTER ROLE super SET pg_audit.log = 'Role';
+ALTER ROLE super SET pg_audit.log_level = 'notice';
+\connect contrib_regression super;
+
+--
+-- Create auditor role
+CREATE ROLE auditor;
+
+--
+-- Create first test user
+CREATE USER user1;
+ALTER ROLE user1 SET pg_audit.log = 'ddl, ROLE';
+ALTER ROLE user1 SET pg_audit.log_level = 'notice';
+
+--
+-- Create, select, drop (select will not be audited)
+\connect contrib_regression user1
+CREATE TABLE public.test (id INT);
+SELECT * FROM test;
+DROP TABLE test;
+
+--
+-- Create second test user
+\connect contrib_regression super
+
+CREATE USER user2;
+ALTER ROLE user2 SET pg_audit.log = 'Read, writE';
+ALTER ROLE user2 SET pg_audit.log_catalog = OFF;
+ALTER ROLE user2 SET pg_audit.log_level = 'warning';
+ALTER ROLE user2 SET pg_audit.role = auditor;
+ALTER ROLE user2 SET pg_audit.log_statement_once = ON;
+
+\connect contrib_regression user2
+CREATE TABLE test2 (id INT);
+GRANT SELECT ON TABLE public.test2 TO auditor;
+
+--
+-- Role-based tests
+CREATE TABLE test3
+(
+	id INT
+);
+
+SELECT count(*)
+  FROM
+(
+	SELECT relname
+	  FROM pg_class
+	  LIMIT 1
+) SUBQUERY;
+
+SELECT *
+  FROM test3, test2;
+
+GRANT INSERT
+   ON TABLE public.test3
+   TO auditor;
+
+--
+-- Create a view to test logging
+CREATE VIEW vw_test3 AS
+SELECT *
+  FROM test3;
+
+GRANT SELECT
+   ON vw_test3
+   TO auditor;
+
+--
+-- Object logged because of:
+-- select on vw_test3
+-- select on test2
+SELECT *
+  FROM vw_test3, test2;
+
+--
+-- Object logged because of:
+-- insert on test3
+-- select on test2
+WITH CTE AS
+(
+	SELECT id
+	  FROM test2
+)
+INSERT INTO test3
+SELECT id
+  FROM cte;
+
+--
+-- Object logged because of:
+-- insert on test3
+WITH CTE AS
+(
+	INSERT INTO test3 VALUES (1)
+				   RETURNING id
+)
+INSERT INTO test2
+SELECT id
+  FROM cte;
+
+GRANT UPDATE ON TABLE public.test2 TO auditor;
+
+--
+-- Object logged because of:
+-- insert on test3
+-- update on test2
+WITH CTE AS
+(
+	UPDATE test2
+	   SET id = 1
+	RETURNING id
+)
+INSERT INTO test3
+SELECT id
+  FROM cte;
+
+--
+-- Object logged because of:
+-- insert on test2
+WITH CTE AS
+(
+	INSERT INTO test2 VALUES (1)
+				   RETURNING id
+)
+UPDATE test3
+   SET id = cte.id
+  FROM cte
+ WHERE test3.id <> cte.id;
+
+--
+-- Change permissions of user 2 so that only object logging will be done
+\connect contrib_regression super
+alter role user2 set pg_audit.log = 'NONE';
+
+\connect contrib_regression user2
+
+--
+-- Create test4 and add permissions
+CREATE TABLE test4
+(
+	id int,
+	name text
+);
+
+GRANT SELECT (name)
+   ON TABLE public.test4
+   TO auditor;
+
+GRANT UPDATE (id)
+   ON TABLE public.test4
+   TO auditor;
+
+GRANT insert (name)
+   ON TABLE public.test4
+   TO auditor;
+
+--
+-- Not object logged
+SELECT id
+  FROM public.test4;
+
+--
+-- Object logged because of:
+-- select (name) on test4
+SELECT name
+  FROM public.test4;
+
+--
+-- Not object logged
+INSERT INTO public.test4 (id)
+				  VALUES (1);
+
+--
+-- Object logged because of:
+-- insert (name) on test4
+INSERT INTO public.test4 (name)
+				  VALUES ('test');
+
+--
+-- Not object logged
+UPDATE public.test4
+   SET name = 'foo';
+
+--
+-- Object logged because of:
+-- update (id) on test4
+UPDATE public.test4
+   SET id = 1;
+
+--
+-- Object logged because of:
+-- update (name) on test4
+-- update (name) takes precedence over select (name) due to ordering
+update public.test4 set name = 'foo' where name = 'bar';
+
+--
+-- Drop test tables
+DROP TABLE test2;
+DROP VIEW vw_test3;
+DROP TABLE test3;
+DROP TABLE test4;
+
+--
+-- Change permissions of user 1 so that session logging will be done
+\connect contrib_regression super
+alter role user1 set pg_audit.log = 'DDL, READ';
+\connect contrib_regression user1
+
+--
+-- Create table is session logged
+CREATE TABLE public.account
+(
+	id INT,
+	name TEXT,
+	password TEXT,
+	description TEXT
+);
+
+--
+-- Select is session logged
+SELECT *
+  FROM account;
+
+--
+-- Insert is not logged
+INSERT INTO account (id, name, password, description)
+			 VALUES (1, 'user1', 'HASH1', 'blah, blah');
+
+--
+-- Change permissions of user 1 so that only object logging will be done
+\connect contrib_regression super
+alter role user1 set pg_audit.log = 'none';
+alter role user1 set pg_audit.role = 'auditor';
+\connect contrib_regression user1
+
+--
+-- ROLE class not set, so auditor grants not logged
+GRANT SELECT (password),
+	  UPDATE (name, password)
+   ON TABLE public.account
+   TO auditor;
+
+--
+-- Not object logged
+SELECT id,
+	   name
+  FROM account;
+
+--
+-- Object logged because of:
+-- select (password) on account
+SELECT password
+  FROM account;
+
+--
+-- Not object logged
+UPDATE account
+   SET description = 'yada, yada';
+
+--
+-- Object logged because of:
+-- update (password) on account
+UPDATE account
+   SET password = 'HASH2';
+
+--
+-- Change permissions of user 1 so that session relation logging will be done
+\connect contrib_regression super
+alter role user1 set pg_audit.log_relation = on;
+alter role user1 set pg_audit.log = 'read, WRITE';
+\connect contrib_regression user1
+
+--
+-- Not logged
+create table ACCOUNT_ROLE_MAP
+(
+	account_id INT,
+	role_id INT
+);
+
+--
+-- ROLE class not set, so auditor grants not logged
+GRANT SELECT
+   ON TABLE public.account_role_map
+   TO auditor;
+
+--
+-- Object logged because of:
+-- select (password) on account
+-- select on account_role_map
+-- Session logged on all tables because log = read and log_relation = on
+SELECT account.password,
+	   account_role_map.role_id
+  FROM account
+	   INNER JOIN account_role_map
+			on account.id = account_role_map.account_id;
+
+--
+-- Object logged because of:
+-- select (password) on account
+-- Session logged on all tables because log = read and log_relation = on
+SELECT password
+  FROM account;
+
+--
+-- Not object logged
+-- Session logged on all tables because log = read and log_relation = on
+UPDATE account
+   SET description = 'yada, yada';
+
+--
+-- Object logged because of:
+-- select (password) on account (in the where clause)
+-- Session logged on all tables because log = read and log_relation = on
+UPDATE account
+   SET description = 'yada, yada'
+ where password = 'HASH2';
+
+--
+-- Object logged because of:
+-- update (password) on account
+-- Session logged on all tables because log = read and log_relation = on
+UPDATE account
+   SET password = 'HASH2';
+
+--
+-- Change back to superuser to do exhaustive tests
+\connect contrib_regression super
+SET pg_audit.log = 'ALL';
+SET pg_audit.log_level = 'notice';
+SET pg_audit.log_relation = ON;
+SET pg_audit.log_parameter = ON;
+
+--
+-- Simple DO block
+DO $$
+BEGIN
+	raise notice 'test';
+END $$;
+
+--
+-- Create test schema
+CREATE SCHEMA test;
+
+--
+-- Copy account to stdout
+COPY account TO stdout;
+
+--
+-- Create a table from a query
+CREATE TABLE test.account_copy AS
+SELECT *
+  FROM account;
+
+--
+-- Copy from stdin to account copy
+COPY test.account_copy from stdin;
+1	user1	HASH2	yada, yada
+\.
+
+--
+-- Test prepared statement
+PREPARE pgclassstmt (oid) AS
+SELECT *
+  FROM account
+ WHERE id = $1;
+
+EXECUTE pgclassstmt (1);
+DEALLOCATE pgclassstmt;
+
+--
+-- Test cursor
+BEGIN;
+
+DECLARE ctest SCROLL CURSOR FOR
+SELECT count(*)
+  FROM
+(
+	SELECT relname
+	  FROM pg_class
+	 LIMIT 1
+ ) subquery;
+
+FETCH NEXT FROM ctest;
+CLOSE ctest;
+COMMIT;
+
+--
+-- Turn off log_catalog and pg_class will not be logged
+SET pg_audit.log_catalog = OFF;
+
+SELECT count(*)
+  FROM
+(
+	SELECT relname
+	  FROM pg_class
+	 LIMIT 1
+ ) subquery;
+
+--
+-- Test prepared insert
+CREATE TABLE test.test_insert
+(
+	id INT
+);
+
+PREPARE pgclassstmt (oid) AS
+INSERT INTO test.test_insert (id)
+					  VALUES ($1);
+EXECUTE pgclassstmt (1);
+
+--
+-- Check that primary key creation is logged
+CREATE TABLE public.test
+(
+	id INT,
+	name TEXT,
+	description TEXT,
+	CONSTRAINT test_pkey PRIMARY KEY (id)
+);
+
+--
+-- Check that analyze is logged
+ANALYZE test;
+
+--
+-- Grants to public should not cause object logging (session logging will
+-- still happen)
+GRANT SELECT
+  ON TABLE public.test
+  TO PUBLIC;
+
+SELECT *
+  FROM test;
+
+-- Check that statements without columns log
+SELECT
+  FROM test;
+
+SELECT 1,
+	   current_user;
+
+DO $$
+DECLARE
+	test INT;
+BEGIN
+	SELECT 1
+	  INTO test;
+END $$;
+
+explain select 1;
+
+--
+-- Test that looks inside of do blocks log
+INSERT INTO TEST (id)
+		  VALUES (1);
+INSERT INTO TEST (id)
+		  VALUES (2);
+INSERT INTO TEST (id)
+		  VALUES (3);
+
+DO $$
+DECLARE
+	result RECORD;
+BEGIN
+	FOR result IN
+		SELECT id
+		  FROM test
+	LOOP
+		INSERT INTO test (id)
+			 VALUES (result.id + 100);
+	END LOOP;
+END $$;
+
+--
+-- Test obfuscated dynamic sql for clean logging
+DO $$
+DECLARE
+	table_name TEXT = 'do_table';
+BEGIN
+	EXECUTE 'CREATE TABLE ' || table_name || ' ("weird name" INT)';
+	EXECUTE 'DROP table ' || table_name;
+END $$;
+
+--
+-- Generate an error and make sure the stack gets cleared
+DO $$
+BEGIN
+	CREATE TABLE bogus.test_block
+	(
+		id INT
+	);
+END $$;
+
+--
+-- Test alter table statements
+ALTER TABLE public.test
+	DROP COLUMN description ;
+
+ALTER TABLE public.test
+	RENAME TO test2;
+
+ALTER TABLE public.test2
+	SET SCHEMA test;
+
+ALTER TABLE test.test2
+	ADD COLUMN description TEXT;
+
+ALTER TABLE test.test2
+	DROP COLUMN description;
+
+DROP TABLE test.test2;
+
+--
+-- Test multiple statements with one semi-colon
+CREATE SCHEMA foo
+	CREATE TABLE foo.bar (id int)
+	CREATE TABLE foo.baz (id int);
+
+--
+-- Test aggregate
+CREATE FUNCTION public.int_add
+(
+	a INT,
+	b INT
+)
+	RETURNS INT LANGUAGE plpgsql AS $$
+BEGIN
+	return a + b;
+END $$;
+
+SELECT int_add(1, 1);
+
+CREATE AGGREGATE public.sum_test(INT) (SFUNC=public.int_add, STYPE=INT, INITCOND='0');
+ALTER AGGREGATE public.sum_test(integer) RENAME TO sum_test2;
+
+--
+-- Test conversion
+CREATE CONVERSION public.conversion_test FOR 'SQL_ASCII' TO 'MULE_INTERNAL' FROM pg_catalog.ascii_to_mic;
+ALTER CONVERSION public.conversion_test RENAME TO conversion_test2;
+
+--
+-- Test create/alter/drop database
+CREATE DATABASE contrib_regression_pgaudit;
+ALTER DATABASE contrib_regression_pgaudit RENAME TO contrib_regression_pgaudit2;
+DROP DATABASE contrib_regression_pgaudit2;
+
+--
+-- Test that frees a memory context earlier than expected
+CREATE TABLE hoge
+(
+	id int
+);
+
+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 ;
+
+SELECT test();
+
+--
+-- Delete all rows then delete 1 row
+SET pg_audit.log = 'write';
+SET pg_audit.role = 'auditor';
+
+create table bar
+(
+	col int
+);
+
+grant delete
+   on bar
+   to auditor;
+
+insert into bar (col)
+		 values (1);
+delete from bar;
+
+insert into bar (col)
+		 values (1);
+delete from bar
+ where col = 1;
+
+drop table bar;
+
+--
+-- Grant roles to each other
+SET pg_audit.log = 'role';
+GRANT user1 TO user2;
+REVOKE user1 FROM user2;
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index 49a6ce8b0b6595a04108a15c1839d728a1e97877..0a2bae8dad9d33ef6d1dd5c4b03640e04490cd25 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 6268d5496bdee6d3a3f78468f50768fe09f4a14b..03fea3217e4eed78474856827652fe7c8ba25009 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -126,6 +126,7 @@
 <!ENTITY oid2name        SYSTEM "oid2name.sgml">
 <!ENTITY pageinspect     SYSTEM "pageinspect.sgml">
 <!ENTITY passwordcheck   SYSTEM "passwordcheck.sgml">
+<!ENTITY pgaudit         SYSTEM "pgaudit.sgml">
 <!ENTITY pgbuffercache   SYSTEM "pgbuffercache.sgml">
 <!ENTITY pgcrypto        SYSTEM "pgcrypto.sgml">
 <!ENTITY pgfreespacemap  SYSTEM "pgfreespacemap.sgml">
diff --git a/doc/src/sgml/pgaudit.sgml b/doc/src/sgml/pgaudit.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..b8df0d50b8b3e155d390e0a74edc5a31e7139dae
--- /dev/null
+++ b/doc/src/sgml/pgaudit.sgml
@@ -0,0 +1,678 @@
+<!-- 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> extension provides detailed session
+    and/or object audit logging via the standard logging facility.  The goal
+    is to provide the tools needed to produce audit logs required to pass any
+    government, financial, or ISO certification audit.
+  </para>
+
+  <para>
+    An audit is an official inspection of an individual's or organization's
+    accounts, typically by an independent body.  The information gathered by
+    <filename>pg_audit</filename> is properly called an audit trail or audit
+    log.  The term audit log is used in this documentation.
+  </para>
+
+  <sect2>
+    <title>Why <literal>pg_audit</>?</title>
+
+    <para>
+      Basic statement logging can be provided by the standard logging facility
+      using <literal>log_statement = all</>.  This is acceptable for monitoring
+      and other usages but does not provide the level of detail generally
+      required for an audit.  It is not enough to have a list of all the
+      operations performed against the database. It must also be possible to
+      find particular statements that are of interest to an auditor.  The
+      standard logging facility shows what the user requested, while
+      <literal>pg_audit</> focuses on the details of what happened while
+      the database was satisfying the request.
+    </para>
+
+    <para>
+      For example, an auditor may want to verify that a particular table was
+      created inside a documented maintenance window.  This might seem like a
+      simple job for grep, but what if you are presented with something like
+      this (intentionally obfuscated) example:
+    </para>
+
+    <programlisting>
+DO $$
+BEGIN
+    EXECUTE 'CREATE TABLE import' || 'ant_table (id INT)';
+END $$;
+    </programlisting>
+
+    <para>
+      Standard logging will give you this:
+    </para>
+
+    <programlisting>
+LOG:  statement: DO $$
+BEGIN
+    EXECUTE 'CREATE TABLE import' || 'ant_table (id INT)';
+END $$;
+    </programlisting>
+
+    <para>
+      It appears that finding the table of interest may require some knowledge
+      of the code in cases where tables are created dynamically.  This is not
+      ideal since it would be preferrable to just search on the table name.
+      This is where <literal>pg_audit</> comes in.  For the same input,
+      it will produce this output in the log:
+    </para>
+
+    <programlisting>
+AUDIT: SESSION,33,1,FUNCTION,DO,,,"DO $$
+BEGIN
+    EXECUTE 'CREATE TABLE import' || 'ant_table (id INT)';
+END $$;"
+AUDIT: SESSION,33,2,DDL,CREATE TABLE,TABLE,public.important_table,CREATE TABLE important_table (id INT)
+    </programlisting>
+
+    <para>
+      Not only is the <literal>DO</> block logged, but substatement 2 contains
+      the full text of the <literal>CREATE TABLE</> with the statement type,
+      object type, and full-qualified name to make searches easy.
+    </para>
+
+    <para>
+      When logging <literal>SELECT</> and <literal>DML</> statements,
+      <literal>pg_audit</> can be configured to log a separate entry for each
+      relation referenced in a statement.  No parsing is required to find all
+      statements that touch a particular table.  In fact, the goal is that the
+      statement text is provided primarily for deep forensics and should not be
+      the required for an audit.
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Usage Considerations</title>
+
+    <para>
+      Depending on settings, it is possible for <literal>pg_audit</literal> to
+      generate an enormous volume of logging.  Be careful to determine
+      exactly what needs to be audit logged in your environment to avoid
+      logging too much.
+    </para>
+
+    <para>
+      For example, when working in an OLAP environment it would probably not be
+      wise to audit log inserts into a large fact table.  The size of the log
+      file will likely be many times the actual data size of the inserts because
+      the log file is expressed as text.  Since logs are generally stored with
+      the OS this may lead to disk space being exhausted very
+      quickly.  In cases where it is not possible to limit audit logging to
+      certain tables, be sure to assess the performance impact while testing
+      and allocate plenty of space on the log volume.  This may also be true for
+      OLTP environments.  Even if the insert volume is not as high, the
+      performance impact of audit logging may still noticeably affect latency.
+    </para>
+
+    <para>
+      To limit the number of relations audit logged for <literal>SELECT</>
+      and <literal>DML</> statments, consider using object audit logging
+      (see <xref linkend="pgaudit-object-audit-logging">).  Object audit logging
+      allows selection of the relations to be logged allowing for reduction
+      of the overall log volume.  However, when new relations are added they
+      must be explicitly added to object audit logging.  A programmatic
+      solution where specified tables are excluded from logging and all others
+      are included may be a good option in this case.
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Settings</title>
+
+    <para>
+      Settings may be modified only by a superuser. Allowing normal users to
+      change their settings would defeat the point of an audit log.
+    </para>
+
+    <para>
+      Settings can be specified globally (in
+      <filename>postgresql.conf</filename> or using
+      <literal>ALTER SYSTEM ... SET</>), at the database level (using
+      <literal>ALTER DATABASE ... SET</literal>), or at the role level (using
+      <literal>ALTER ROLE ... SET</literal>).  Note that settings are not
+      inherited through normal role inheritance and <literal>SET ROLE</> will
+      not alter a user's <literal>pg_audit</> settings.  This is a limitation
+      of the roles system and not inherent to <literal>pg_audit</>.
+    </para>
+
+    <para>
+      The <literal>pg_audit</> extension must be loaded in
+      <xref linkend="guc-shared-preload-libraries">.  Otherwise, an error
+      will be raised at load time and no audit logging will occur.
+    </para>
+
+    <variablelist>
+      <varlistentry id="guc-pgaudit-log" xreflabel="pg_audit.log">
+        <term><varname>pg_audit.log</varname> (<type>string</type>)
+          <indexterm>
+            <primary><varname>pg_audit.log</> configuration parameter</primary>
+          </indexterm>
+        </term>
+        <listitem>
+          <para>
+            Specifies which classes of statements will be logged by session
+            audit logging.  Possible values are:
+          </para>
+
+          <itemizedlist>
+            <listitem>
+              <para>
+                <literal>READ</literal> - <literal>SELECT</literal> and
+                <literal>COPY</literal> when the source is a relation or a
+                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 relation.
+              </para>
+            </listitem>
+            <listitem>
+              <para>
+                <literal>FUNCTION</literal> - Function calls and
+                <literal>DO</literal> blocks.
+              </para>
+            </listitem>
+            <listitem>
+              <para>
+                <literal>ROLE</literal> - Statements related to roles and
+                privileges: <literal>GRANT</literal>,
+                <literal>REVOKE</literal>,
+                <literal>CREATE/ALTER/DROP ROLE</literal>.
+              </para>
+            </listitem>
+            <listitem>
+              <para>
+                <literal>DDL</literal> - All <literal>DDL</> that is not included
+                in the <literal>ROLE</> class plus <literal>REINDEX</>.
+              </para>
+            </listitem>
+            <listitem>
+              <para>
+                <literal>MISC</literal> - Miscellaneous commands, e.g.
+                <literal>DISCARD</literal>, <literal>FETCH</literal>,
+                <literal>CHECKPOINT</literal>, <literal>VACUUM</literal>.
+              </para>
+            </listitem>
+          </itemizedlist>
+
+          <para>
+            Multiple classes can be provided using a comma-separated list and
+            classes can be subtracted by prefacing the class with a
+            <literal>-</> sign (see <xref linkend="pgaudit-session-audit-logging">).
+            The default is <literal>none</>.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry id="guc-pgaudit-log-catalog" xreflabel="pg_audit.log_catalog">
+        <term><varname>pg_audit.log_catalog</varname> (<type>boolean</type>)
+          <indexterm>
+            <primary><varname>pg_audit.log_catalog</> configuration parameter</primary>
+          </indexterm>
+        </term>
+        <listitem>
+          <para>
+            Specifies that session logging should be enabled in the case where all
+            relations in a statement are in pg_catalog.  Disabling this setting
+            will reduce noise in the log from tools like psql and PgAdmin that query
+            the catalog heavily. The default is <literal>on</>.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry id="guc-pgaudit-log-level" xreflabel="pg_audit.log_level">
+        <term><varname>pg_audit.log_level</varname> (<type>boolean</type>)
+          <indexterm>
+            <primary><varname>pg_audit.log_level</> configuration parameter</primary>
+          </indexterm>
+        </term>
+        <listitem>
+          <para>
+            Specifies the log level that will be used for log entries (see
+            <xref linkend="RUNTIME-CONFIG-SEVERITY-LEVELS"> for valid levels).
+            This setting is used for regression testing and may also be useful
+            to end users for testing or other purposes.  It is not intended to
+            be used in a production environment as it may leak which statements
+            are being logged to the user. The default is <literal>log</>.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry id="guc-pgaudit-log-parameter" xreflabel="pg_audit.log_parameter">
+        <term><varname>pg_audit.log_parameter</varname> (<type>boolean</type>)
+          <indexterm>
+            <primary><varname>pg_audit.log_parameter</> configuration parameter</primary>
+          </indexterm>
+        </term>
+        <listitem>
+          <para>
+            Specifies that audit logging should include the parameters that
+            were passed with the statement.  When parameters are present they will
+            be included in CSV format after the statement text. The default is
+            <literal>off</>.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry id="guc-pgaudit-log-relation" xreflabel="pg_audit.log_relation">
+        <term><varname>pg_audit.log_relation</varname> (<type>boolean</type>)
+          <indexterm>
+            <primary><varname>pg_audit.log_relation</> configuration parameter</primary>
+          </indexterm>
+        </term>
+        <listitem>
+          <para>
+            Specifies whether session audit logging should create a separate
+            log entry for each relation (<literal>TABLE</>, <literal>VIEW</>,
+            etc.) referenced in a <literal>SELECT</> or <literal>DML</>
+            statement.  This is a useful shortcut for exhaustive logging
+            without using object audit logging.  The default is
+            <literal>off</>.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry id="guc-pgaudit-log-statement-once" xreflabel="pg_audit.log_statement-once">
+        <term><varname>pg_audit.log_statement_once</varname> (<type>boolean</type>)
+          <indexterm>
+            <primary><varname>pg_audit.log_statement_once</> configuration parameter</primary>
+          </indexterm>
+        </term>
+        <listitem>
+          <para>
+            Specifies whether logging will include the statement text and
+            parameters with the first log entry for a statement/substatement
+            combination or with every entry.  Disabling this setting will
+            result in less verbose logging but may make it more difficult to
+            determine the statement that generated a log entry, though the
+            statement/substatement pair along with the process id should suffice
+            to identify the statement text logged with a previous entry.  The
+            default is <literal>off</>.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry id="guc-pgaudit-role" xreflabel="pg_audit.role">
+        <term><varname>pg_audit.role</varname> (<type>string</type>)
+          <indexterm>
+            <primary><varname>pg_audit.role</> configuration parameter</primary>
+          </indexterm>
+        </term>
+        <listitem>
+          <para>
+            Specifies the master role to use for object audit logging.  Muliple
+            audit roles can be defined by granting them to the master role.
+            This allows multiple groups to be in charge of different aspects
+            of audit logging.  There is no default.
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </sect2>
+
+  <sect2 id="pgaudit-session-audit-logging">
+    <title>Session Audit Logging</title>
+
+    <para>
+      Session audit logging provides detailed logs of all statements executed
+      by a user in the backend.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Session logging is enabled with the <xref linkend="guc-pgaudit-log">
+        setting.
+
+        Enable session logging for all <literal>DML</> and <literal>DDL</> and
+        log all relations in <literal>DML</> statements:
+          <programlisting>
+set pg_audit.log = 'write, ddl';
+set pg_audit.log_relation = on;
+          </programlisting>
+      </para>
+
+      <para>
+        Enable session logging for all commands except <literal>MISC</> and
+        raise audit log messages as <literal>NOTICE</>:
+          <programlisting>
+set pg_audit.log = 'all, -misc';
+set pg_audit.log_notice = on;
+          </programlisting>
+      </para>
+    </sect3>
+
+    <sect3>
+      <title>Example</title>
+
+      <para>
+        In this example session audit logging is used for logging
+        <literal>DDL</> and <literal>SELECT</> statements.  Note that the
+        insert statement is not logged since the <literal>WRITE</> class
+        is not enabled
+      </para>
+
+      <para>
+        SQL:
+      </para>
+      <programlisting>
+set pg_audit.log = 'read, ddl';
+
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+insert into account (id, name, password, description)
+             values (1, 'user1', 'HASH1', 'blah, blah');
+
+select *
+    from account;
+      </programlisting>
+
+      <para>
+        Log Output:
+      </para>
+
+      <programlisting>
+AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.account,create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+AUDIT: SESSION,2,1,READ,SELECT,,,select *
+    from account
+      </programlisting>
+    </sect3>
+  </sect2>
+
+  <sect2 id="pgaudit-object-audit-logging">
+    <title>Object Auditing</title>
+
+    <para>
+      Object audit logging logs statements that affect a particular relation.
+      Only <literal>SELECT</>, <literal>INSERT</>, <literal>UPDATE</> and
+      <literal>DELETE</> commands are supported.  <literal>TRUNCATE</> is not
+      included because there is no specific privilege for it.
+    </para>
+
+    <para>
+      Object audit logging is intended to be a finer-grained replacement for
+      <literal>pg_audit.log = 'read, write'</literal>.  As such, it may not
+      make sense to use them in conjunction but one possible scenario would
+      be to use session logging to capture each statement and then supplement
+      that with object logging to get more detail about specific relations.
+    </para>
+
+    <sect3>
+      <title>Configuration</title>
+
+      <para>
+        Object-level audit logging is implemented via the roles system.  The
+        <xref linkend="guc-pgaudit-role"> setting defines the role that
+        will be used for audit logging.  A relation (<literal>TABLE</>,
+        <literal>VIEW</>, etc.) will be audit logged when the audit role has
+        permissions for the command executed or inherits the permissions from
+        another role.  This allows you to effectively have multiple audit roles
+        even though there is a single master role in any context.
+      </para>
+
+      <para>
+      Set <xref linkend="guc-pgaudit-role"> to <literal>auditor</> and
+      grant <literal>SELECT</> and <literal>DELETE</> privileges on the
+      <literal>account</> table.  Any <literal>SELECT</> or
+      <literal>DELETE</> statements on <literal>account</> will now be
+      logged:
+      </para>
+
+      <programlisting>
+set pg_audit.role = 'auditor';
+
+grant select, delete
+   on public.account
+   to auditor;
+      </programlisting>
+    </sect3>
+
+    <sect3>
+      <title>Example</title>
+
+      <para>
+        In this example object audit logging is used to illustrate how a
+        granular approach may be taken towards logging of <literal>SELECT</>
+        and <literal>DML</> statements.  Note that logging on the
+        <literal>account</> table is controlled by column-level permissions,
+        while logging on <literal>account_role_map</> is table-level.
+      </para>
+
+      <para>
+        SQL:
+      </para>
+
+        <programlisting>
+set pg_audit.role = 'auditor';
+
+create table account
+(
+    id int,
+    name text,
+    password text,
+    description text
+);
+
+grant select (password)
+   on public.account
+   to auditor;
+
+select id, name
+  from account;
+
+select password
+  from account;
+
+grant update (name, password)
+   on public.account
+   to auditor;
+
+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 auditor;
+
+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,1,1,READ,SELECT,TABLE,public.account,select password
+  from account
+AUDIT: OBJECT,2,1,WRITE,UPDATE,TABLE,public.account,update account
+   set password = 'HASH2'
+AUDIT: OBJECT,3,1,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,3,1,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 compliant CSV format only if the log line prefix portion
+          of each log entry is removed.
+        </para>
+      </note>
+
+      <itemizedlist>
+        <listitem>
+          <para>
+            <literal>AUDIT_TYPE</> - <literal>SESSION</> or
+            <literal>OBJECT</>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT_ID</> - Unique statement ID for this session.
+            Each statement ID represents a backend call.  Statement IDs are
+            sequential even if some statements are not logged.  There may be
+            multiple entries for a statement ID when more than one relation
+            is logged.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>SUBSTATEMENT_ID</> - Sequential ID for each
+            substatement within the main statement.  For example, calling
+            a function from a query.  Substatement IDs are continuous
+            even if some substatements are not logged.  There may be multiple
+            entries for a substatement ID when more than one relation is logged.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>CLASS</> - e.g. (<literal>READ</>,
+            <literal>ROLE</>) (see <xref linkend="guc-pgaudit-log">).
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>COMMAND</> - e.g. <literal>ALTER TABLE</>,
+            <literal>SELECT</>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_TYPE</> - <literal>TABLE</>,
+            <literal>INDEX</>, <literal>VIEW</>, etc.
+            Available for <literal>SELECT</>, <literal>DML</> and most
+            <literal>DDL</> statements.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>OBJECT_NAME</> - The fully-qualified object name
+            (e.g. public.account).  Available for <literal>SELECT</>,
+            <literal>DML</> and most <literal>DDL</> statements.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            <literal>STATEMENT</> - Statement executed on the backend.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </para>
+
+    <para>
+      Use <xref linkend="guc-log-line-prefix"> to add any other fields that
+      are needed to satisfy your audit log requirements.  A typical log line
+      prefix might be <literal>'%m %u %d: '</> which would provide the date/time,
+      user name, and database name for each audit log.
+    </para>
+  </sect2>
+
+  <sect2>
+    <title>Caveats</title>
+
+    <itemizedlist>
+      <listitem>
+        <para>
+          Object renames are logged under the name they were renamed to.
+          For example, renaming a table will produce the following result:
+        </para>
+
+        <programlisting>
+ALTER TABLE test RENAME TO test2;
+
+AUDIT: SESSION,36,1,DDL,ALTER TABLE,TABLE,public.test2,ALTER TABLE test RENAME TO test2
+        </programlisting>
+      </listitem>
+
+      <listitem>
+        <para>
+          It is possible to have a command logged more than once.  For example,
+          when a table is created with a primary key specified at creation time
+          the index for the primary key will be logged independently and another
+          audit log will be made for the index under the create entry.  The
+          multiple entries will however be contained within one statement ID.
+        </para>
+      </listitem>
+
+      <listitem>
+        <para>
+          Autovacuum and Autoanalyze are not logged, nor are they intended to be.
+        </para>
+      </listitem>
+
+      <listitem>
+        <para>
+          Statements that are executed after a transaction enters an aborted state
+          will not be audit logged.  However, the statement that caused the error
+          and any subsequent statements executed in the aborted transaction will
+          be logged as ERRORs by the standard logging facility.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </sect2>
+
+  <sect2>
+    <title>Authors</title>
+
+    <para>
+      Abhijit Menon-Sen <email>ams@2ndQuadrant.com</email>, Ian Barwick <email>ian@2ndQuadrant.com</email>, and David Steele <email>david@pgmasters.net</email>.
+    </para>
+  </sect2>
+</sect1>