test_delta_v2_001.diff

text/plain

Filename: test_delta_v2_001.diff
Type: text/plain
Part: 0
Message: Re: delta relations in AFTER triggers

Patch

Format: unified
Series: patch v2
File+
contrib/statement_trigger_row/Makefile 17 0
contrib/statement_trigger_row/sql/easy_way.sql 85 0
contrib/statement_trigger_row/sql/hard_way.sql 68 0
contrib/statement_trigger_row/sql/statement_trigger_row.sql 18 0
contrib/statement_trigger_row/statement_trigger_row--1.0.sql 10 0
contrib/statement_trigger_row/statement_trigger_row.c 140 0
contrib/statement_trigger_row/statement_trigger_row.control 5 0
diff --git a/contrib/statement_trigger_row/Makefile b/contrib/statement_trigger_row/Makefile
new file mode 100644
index 0000000..e0cf006
--- /dev/null
+++ b/contrib/statement_trigger_row/Makefile
@@ -0,0 +1,17 @@
+# contrib/statement_trigger_row/Makefile
+
+MODULES = statement_trigger_row
+
+EXTENSION = statement_trigger_row
+DATA = statement_trigger_row--1.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/statement_trigger_row
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/statement_trigger_row/sql/easy_way.sql b/contrib/statement_trigger_row/sql/easy_way.sql
new file mode 100644
index 0000000..019ae7f
--- /dev/null
+++ b/contrib/statement_trigger_row/sql/easy_way.sql
@@ -0,0 +1,85 @@
+/*
+ * If these were surfaced to PL/pgsql, this is what it might look like.
+ */
+
+BEGIN;
+
+CREATE TABLE a(
+    id SERIAL PRIMARY KEY,
+    i INT
+);
+
+CREATE FUNCTION summarize_a_inserts()
+RETURNS TRIGGER LANGUAGE plpgsql
+AS $$
+DECLARE
+    the_sum BIGINT;
+BEGIN
+    SELECT INTO the_sum sum(NEW.i)
+    FROM
+        new_a;
+    RAISE NOTICE 'Total change: %.', the_sum;
+    RETURN NULL;
+END;
+$$;
+
+CREATE FUNCTION summarize_a_updates()
+RETURNS TRIGGER LANGUAGE plpgsql
+AS $$
+DECLARE
+    the_sum BIGINT;
+BEGIN
+    SELECT INTO the_sum sum(COALESCE(NEW.i,0) - COALESCE(OLD.i, 0))
+    FROM
+        old_a
+    JOIN
+        new_a
+        ON(old_a.id = new_a.id);
+    RAISE NOTICE 'Total change: %.', the_sum;
+    RETURN NULL;
+END;
+$$;
+
+CREATE FUNCTION summarize_a_deletes()
+RETURNS TRIGGER LANGUAGE plpgsql
+AS $$
+DECLARE
+    the_sum BIGINT;
+BEGIN
+    SELECT INTO the_sum -1 * sum(OLD.i)
+    FROM
+        old_a;
+    RAISE NOTICE 'Total change: %.', the_sum;
+    RETURN NULL;
+END;
+$$;
+
+CREATE TRIGGER statement_after_insert_a
+    AFTER INSERT ON a
+    REFERENCING
+        NEW TABLE AS new_a
+    FOR EACH STATEMENT
+        EXECUTE PROCEDURE summarize_a_inserts();
+
+CREATE TRIGGER statement_after_update_a
+    AFTER UPDATE ON a
+    REFERENCING
+        OLD TABLE AS old_a
+        NEW TABLE AS new_a
+    FOR EACH STATEMENT
+        EXECUTE PROCEDURE summarize_a_updates();
+
+CREATE TRIGGER statement_after_delete_a
+    AFTER DELETE ON a
+    REFERENCING
+        OLD TABLE AS old_a
+    FOR EACH STATEMENT
+        EXECUTE PROCEDURE summarize_a_deletes();
+
+INSERT INTO a(i)
+SELECT * FROM generate_series(1,10000);
+
+UPDATE a SET i=i+1;
+
+ROLLBACK;
+
diff --git a/contrib/statement_trigger_row/sql/hard_way.sql b/contrib/statement_trigger_row/sql/hard_way.sql
new file mode 100644
index 0000000..c6f7c1d
--- /dev/null
+++ b/contrib/statement_trigger_row/sql/hard_way.sql
@@ -0,0 +1,68 @@
+CREATE TABLE IF NOT EXISTS h(
+    i INTEGER
+);
+
+CREATE FUNCTION set_up_h_rows()
+RETURNS TRIGGER LANGUAGE plpgsql
+AS $$
+BEGIN
+    CREATE TEMPORARY TABLE IF NOT EXISTS h_rows(LIKE a) ON COMMIT DROP;
+    RETURN NULL;
+END;
+$$;
+
+CREATE TRIGGER statement_before_writing_h
+    BEFORE INSERT OR UPDATE OR DELETE ON a
+    FOR EACH STATEMENT
+        EXECUTE PROCEDURE set_up_h_rows();
+
+CREATE OR REPLACE FUNCTION stash_h_row_deltas()
+RETURNS TRIGGER
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    INSERT INTO h_rows(i)
+    VALUES(
+        CASE TG_OP
+            WHEN 'INSERT' THEN COALESCE(NEW.i,0)
+            WHEN 'UPDATE' THEN COALESCE(NEW.i,0) - COALESCE(OLD.i,0)
+            WHEN 'DELETE' THEN -1 * COALESCE(OLD.i,0)
+        END
+    );
+    IF TG_OP IN ('INSERT','UPDATE')
+        THEN
+            RETURN NEW;
+        ELSE
+            RETURN OLD;
+    END IF;
+END;
+$$;
+
+CREATE TRIGGER during_trg
+    BEFORE INSERT OR UPDATE OR DELETE ON a
+    FOR EACH ROW
+        EXECUTE PROCEDURE stash_h_row_deltas();
+
+CREATE FUNCTION summarize_h_rows()
+RETURNS TRIGGER LANGUAGE plpgsql
+AS $$
+DECLARE the_sum BIGINT;
+BEGIN
+    SELECT INTO the_sum sum(i) FROM h_rows;
+    RAISE NOTICE 'Total change: %.', the_sum;
+    TRUNCATE h_rows;
+    RETURN NULL;
+END;
+$$;
+
+CREATE TRIGGER statement_after_writing_h
+    AFTER INSERT OR UPDATE OR DELETE ON a
+    FOR EACH STATEMENT
+        EXECUTE PROCEDURE summarize_h_rows();
+
+INSERT INTO h(i)
+SELECT * FROM generate_series(1,10000);
+
+UPDATE h SET i=i+1;
+
+DELETE FROM h WHERE i < 5000;
diff --git a/contrib/statement_trigger_row/sql/statement_trigger_row.sql b/contrib/statement_trigger_row/sql/statement_trigger_row.sql
new file mode 100644
index 0000000..4d5925a
--- /dev/null
+++ b/contrib/statement_trigger_row/sql/statement_trigger_row.sql
@@ -0,0 +1,18 @@
+CREATE TABLE IF NOT EXISTS e(
+    i INT
+);
+
+CREATE TRIGGER statement_dml_e
+    AFTER INSERT OR UPDATE OR DELETE ON e
+    REFERENCING
+        OLD TABLE AS old_e
+        NEW TABLE AS new_e
+    FOR EACH STATEMENT
+        EXECUTE PROCEDURE statement_trigger_row();
+
+INSERT INTO e(i)
+SELECT * FROM generate_series(1,10000);
+
+UPDATE e SET i=i+1;
+
+DELETE FROM e WHERE i < 5000;
diff --git a/contrib/statement_trigger_row/statement_trigger_row--1.0.sql b/contrib/statement_trigger_row/statement_trigger_row--1.0.sql
new file mode 100644
index 0000000..ffc0e36
--- /dev/null
+++ b/contrib/statement_trigger_row/statement_trigger_row--1.0.sql
@@ -0,0 +1,10 @@
+/* contrib/statement_trigger_row--1.0.sql */
+
+
+-- Complain if script is sourced in psql, rather than via CREATE EXTENSION.
+\echo Use "CREATE EXTENSION statement_trigger_row" to load this file.  \quit
+
+CREATE FUNCTION statement_trigger_row()
+RETURNS pg_catalog.trigger
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
diff --git a/contrib/statement_trigger_row/statement_trigger_row.c b/contrib/statement_trigger_row/statement_trigger_row.c
new file mode 100644
index 0000000..048eb3b
--- /dev/null
+++ b/contrib/statement_trigger_row/statement_trigger_row.c
@@ -0,0 +1,140 @@
+/*-------------------------------------------------------------------------
+ *
+ * statement_trigger_row.c
+ * 		statement_trigger_row support for PostgreSQL
+ *
+ * Portions Copyright (c) 2011-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * 		contrib/statement_trigger_row.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "executor/spi.h"
+#include "commands/trigger.h"
+#include "utils/rel.h"
+
+#ifdef PG_MODULE_MAGIC
+PG_MODULE_MAGIC;
+#endif
+
+extern Datum statement_trigger_row(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(statement_trigger_row);
+
+Datum
+statement_trigger_row(PG_FUNCTION_ARGS)
+{
+	TriggerData		*trigdata = (TriggerData *) fcinfo->context;
+	TupleDesc		tupdesc;
+	TupleTableSlot	*slot;
+	Tuplestorestate	*new_tuplestore;
+	Tuplestorestate	*old_tuplestore;
+	int64			delta = 0;
+
+	if (!CALLED_AS_TRIGGER(fcinfo))
+	{
+		ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("statement_trigger_row: not called by trigger manager")));
+	}
+
+	if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
+	{
+		ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("statement_trigger_row: not called by AFTER trigger")));
+	}
+
+	if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+	{
+		ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("You may not call this function in a TRUNCATE trigger.")));
+	}
+
+	tupdesc = trigdata->tg_relation->rd_att;
+
+	slot = MakeSingleTupleTableSlot(tupdesc);
+
+	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+	{
+		if (trigdata->tg_newdelta == NULL)
+			ereport(ERROR,
+					(errmsg("You must include NEW TABLE AS in your CREATE TRIGGER statement")));
+
+		new_tuplestore = trigdata->tg_newdelta;
+		/*
+		 * Ensure that we're at the right place in the tuplestore, as
+		 * other triggers may have messed with the state.
+		 */
+		tuplestore_rescan(new_tuplestore);
+
+		/* Iterate through the new tuples, adding. */
+		while (tuplestore_gettupleslot(new_tuplestore, true, false, slot)) {
+			bool	isnull;
+			Datum	val = slot_getattr(slot, 1, &isnull);
+			if (!isnull)
+				delta += DatumGetInt32(val);
+		}
+	}
+	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+	{
+		if (trigdata->tg_olddelta == NULL)
+			ereport(ERROR,
+					(errmsg("You must include OLD TABLE AS in your CREATE TRIGGER statement")));
+
+		old_tuplestore = trigdata->tg_olddelta;
+		tuplestore_rescan(old_tuplestore);
+		/* Iterate through the old tuples, subtracting. */
+		while (tuplestore_gettupleslot(old_tuplestore, true, false, slot)) {
+			bool	isnull;
+			Datum	val = slot_getattr(slot, 1, &isnull);
+			if (!isnull)
+				delta -= DatumGetInt32(val);
+		}
+	}
+	else /* It's an UPDATE */
+	{
+		if (trigdata->tg_olddelta == NULL)
+			ereport(ERROR,
+					(errmsg("You must include OLD TABLE AS in your CREATE TRIGGER statement")));
+		if (trigdata->tg_newdelta == NULL)
+			ereport(ERROR,
+					(errmsg("You must include NEW TABLE AS in your CREATE TRIGGER statement")));
+
+		old_tuplestore = trigdata->tg_olddelta;
+		new_tuplestore = trigdata->tg_newdelta;
+
+		tuplestore_rescan(old_tuplestore);
+		tuplestore_rescan(new_tuplestore);
+
+		/* Iterate through both the new and old tuples, incrementing
+		 * or decrementing as needed. */
+		while (tuplestore_gettupleslot(new_tuplestore, true, false, slot)) {
+			bool	isnull;
+			Datum	val = slot_getattr(slot, 1, &isnull);
+			if (!isnull)
+				delta += DatumGetInt32(val);
+		}
+
+		while (tuplestore_gettupleslot(old_tuplestore, true, false, slot)) {
+			bool	isnull;
+			Datum	val = slot_getattr(slot, 1, &isnull);
+			if (!isnull)
+				delta -= DatumGetInt32(val);
+		}
+
+	}
+
+	ExecDropSingleTupleTableSlot(slot);
+
+	ereport(NOTICE, (errmsg("Total change: " INT64_FORMAT, delta)));
+
+	return PointerGetDatum(NULL);
+
+}
diff --git a/contrib/statement_trigger_row/statement_trigger_row.control b/contrib/statement_trigger_row/statement_trigger_row.control
new file mode 100644
index 0000000..846ea2d
--- /dev/null
+++ b/contrib/statement_trigger_row/statement_trigger_row.control
@@ -0,0 +1,5 @@
+# statement_trigger_row extension
+comment = 'Statement trigger row'
+default_version = '1.0'
+module_pathname = '$libdir/statement_trigger_row'
+relocatable = true