diff --git a/contrib/Makefile b/contrib/Makefile index b37d0dd..bcd7c28 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -48,6 +48,7 @@ SUBDIRS = \ postgres_fdw \ seg \ spi \ + transition_tables \ tablefunc \ tcn \ test_decoding \ diff --git a/contrib/transition_tables/.gitignore b/contrib/transition_tables/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/transition_tables/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/transition_tables/Makefile b/contrib/transition_tables/Makefile new file mode 100644 index 0000000..8a75350 --- /dev/null +++ b/contrib/transition_tables/Makefile @@ -0,0 +1,20 @@ +# contrib/transition_tables/Makefile + +MODULE_big = transition_tables +OBJS = transition_tables.o + +EXTENSION = transition_tables +DATA = transition_tables--1.0.sql + +REGRESS = transition_tables + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/transition_tables +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/transition_tables/expected/transition_tables.out b/contrib/transition_tables/expected/transition_tables.out new file mode 100644 index 0000000..7dfed35 --- /dev/null +++ b/contrib/transition_tables/expected/transition_tables.out @@ -0,0 +1,18 @@ +CREATE EXTENSION transition_tables; +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 transition_tables(); +INSERT INTO e(i) +SELECT * FROM generate_series(1,10000); +NOTICE: Total change: 50005000 +UPDATE e SET i=i+1; +NOTICE: Total change: 10000 +DELETE FROM e WHERE i < 5000; +NOTICE: Total change: -12497499 diff --git a/contrib/transition_tables/sql/transition_tables.sql b/contrib/transition_tables/sql/transition_tables.sql new file mode 100644 index 0000000..04f8bd7 --- /dev/null +++ b/contrib/transition_tables/sql/transition_tables.sql @@ -0,0 +1,20 @@ +CREATE EXTENSION transition_tables; + +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 transition_tables(); + +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/transition_tables/transition_tables--1.0.sql b/contrib/transition_tables/transition_tables--1.0.sql new file mode 100644 index 0000000..6c1625e --- /dev/null +++ b/contrib/transition_tables/transition_tables--1.0.sql @@ -0,0 +1,10 @@ +/* contrib/transition_tables--1.0.sql */ + + +-- Complain if script is sourced in psql, rather than via CREATE EXTENSION. +\echo Use "CREATE EXTENSION transition_tables" to load this file. \quit + +CREATE FUNCTION transition_tables() +RETURNS pg_catalog.trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; diff --git a/contrib/transition_tables/transition_tables.c b/contrib/transition_tables/transition_tables.c new file mode 100644 index 0000000..2b9264c --- /dev/null +++ b/contrib/transition_tables/transition_tables.c @@ -0,0 +1,144 @@ +/*------------------------------------------------------------------------- + * + * transition_tables.c + * sample of accessing trigger transition tables from a C trigger + * + * Portions Copyright (c) 2011-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/transition_tables.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "commands/trigger.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(transition_tables); + +Datum +transition_tables(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_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("transition_tables: must be called as trigger"))); + + if (!TRIGGER_FIRED_AFTER(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("transition_tables: must be called after the change"))); + + if (!TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("transition_tables: must be called for each statement"))); + + tupdesc = trigdata->tg_relation->rd_att; + slot = MakeSingleTupleTableSlot(tupdesc); + + /* + * Since other code may have already used the tuplestore(s), reset to the + * start before reading. + */ + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + { + if (trigdata->tg_newdelta == NULL) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("NEW TABLE was not specified in the CREATE TRIGGER statement"))); + + new_tuplestore = trigdata->tg_newdelta; + 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, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("OLD TABLE was not specified in the 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 if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + { + if (trigdata->tg_olddelta == NULL) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("OLD TABLE was not specified in the CREATE TRIGGER statement"))); + if (trigdata->tg_newdelta == NULL) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("NEW TABLE was not specified in the 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, adding or subtracting + * 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); + } + + } + else + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("transition_tables: only INSERT, UPDATE, and DELETE triggers may use transition tables"))); + + ExecDropSingleTupleTableSlot(slot); + + ereport(NOTICE, (errmsg("Total change: " INT64_FORMAT, delta))); + + return PointerGetDatum(NULL); /* after trigger; value doesn't matter */ +} diff --git a/contrib/transition_tables/transition_tables.control b/contrib/transition_tables/transition_tables.control new file mode 100644 index 0000000..486dde7 --- /dev/null +++ b/contrib/transition_tables/transition_tables.control @@ -0,0 +1,5 @@ +# transition_tables extension +comment = 'Sample code for using trigger transition tables' +default_version = '1.0' +module_pathname = '$libdir/transition_tables' +relocatable = true