From d75206fdf59d1c0f49137e6b5dd65ea942da35ff Mon Sep 17 00:00:00 2001
From: "Vadim B. Mikheev" <vadim4o@yahoo.com>
Date: Thu, 2 Oct 1997 18:01:57 +0000
Subject: [PATCH] General function for SERIAL/IDENTITY/AUTOINCREMENT feature.
 Handle INSERT event in timetravel().

---
 contrib/spi/Makefile           |   3 +-
 contrib/spi/README             |  56 ++++++++++++------
 contrib/spi/autoinc.c          | 100 +++++++++++++++++++++++++++++++++
 contrib/spi/autoinc.example    |  35 ++++++++++++
 contrib/spi/autoinc.source     |   6 ++
 contrib/spi/timetravel.c       |  50 ++++++++++++++++-
 contrib/spi/timetravel.example |  19 ++++---
 contrib/spi/timetravel.source  |   6 --
 8 files changed, 241 insertions(+), 34 deletions(-)
 create mode 100644 contrib/spi/autoinc.c
 create mode 100644 contrib/spi/autoinc.example
 create mode 100644 contrib/spi/autoinc.source

diff --git a/contrib/spi/Makefile b/contrib/spi/Makefile
index 3f5790afdf..7082762992 100644
--- a/contrib/spi/Makefile
+++ b/contrib/spi/Makefile
@@ -9,7 +9,8 @@ ifdef REFINT_VERBOSE
 CFLAGS+= -DREFINT_VERBOSE
 endif
 
-TARGETS= refint$(DLSUFFIX) refint.sql timetravel$(DLSUFFIX) timetravel.sql
+TARGETS= refint$(DLSUFFIX) refint.sql timetravel$(DLSUFFIX) timetravel.sql \
+		autoinc$(DLSUFFIX) autoinc.sql
 
 CLEANFILES+= $(TARGETS)
 
diff --git a/contrib/spi/README b/contrib/spi/README
index 65868f0fc7..5d332cb585 100644
--- a/contrib/spi/README
+++ b/contrib/spi/README
@@ -8,8 +8,8 @@ table/field names (as described below) while creating a trigger.
 
 check_primary_key () is to used for foreign keys of a table.
    
-   You are to create trigger (BEFORE INSERT OR UPDATE) using this 
-function on a table referencing another table. You are to specify
+   You have to create trigger (BEFORE INSERT OR UPDATE) using this 
+function on a table referencing another table. You have to specify
 as function arguments: triggered table column names which correspond
 to foreign key, referenced table name and column names in referenced
 table which correspond to primary/unique key.
@@ -18,8 +18,8 @@ one reference.
 
 check_foreign_key () is to used for primary/unique keys of a table.
 
-   You are to create trigger (BEFORE DELETE OR UPDATE) using this
-function on a table referenced by another table(s). You are to specify
+   You have to create trigger (BEFORE DELETE OR UPDATE) using this
+function on a table referenced by another table(s). You have to specify
 as function arguments: number of references for which function has to
 performe checking, action if referencing key found ('cascade' - to delete
 corresponding foreign key, 'restrict' - to abort transaction if foreign keys 
@@ -42,20 +42,26 @@ refint.source).
 
    Old internally supported time-travel (TT) used insert/delete
 transaction commit times. To get the same feature using triggers
-you are to add to a table two columns of abstime type to store
+you have to add to a table two columns of abstime type to store
 date when a tuple was inserted (start_date) and changed/deleted 
 (stop_date):
 
 CREATE TABLE XXX (
 	...		...
-	date_on		abstime default currabstime(),
-	date_off	abstime default 'infinity'
+	date_on		abstime,
+	date_off	abstime
 	...		...
 );
 
-- so, tuples being inserted with NULLs in date_on/date_off will get
-_current_date_ in date_on (name of start_date column in XXX) and INFINITY in
-date_off (name of stop_date column in XXX).
+CREATE TRIGGER timetravel
+        BEFORE INSERT OR DELETE OR UPDATE ON tttest
+        FOR EACH ROW
+        EXECUTE PROCEDURE
+        timetravel (date_on, date_off);
+
+   Tuples being inserted with NULLs in date_on/date_off will get current
+date in date_on (name of start_date column in XXX) and INFINITY in date_off
+(name of stop_date column in XXX).
 
    Tuples with stop_date equal INFINITY are "valid now": when trigger will
 be fired for UPDATE/DELETE of a tuple with stop_date NOT equal INFINITY then
@@ -72,7 +78,7 @@ DELETE: new tuple will be inserted with stop_date setted to current date
 (and with the same data in other columns as in tuple being deleted).
 
    NOTE:
-1. To get tuples "valid now" you are to add _stop_date_ = 'infinity'
+1. To get tuples "valid now" you have to add _stop_date_ = 'infinity'
    to WHERE. Internally supported TT allowed to avoid this...
    Fixed rewriting RULEs could help here...
    As work arround you may use VIEWs...
@@ -83,12 +89,9 @@ DELETE: new tuple will be inserted with stop_date setted to current date
 
 timetravel() is general trigger function.
 
-   You are to create trigger BEFORE (!!!) UPDATE OR DELETE using this
-function on a time-traveled table. You are to specify two arguments: name of
-start_date column and name of stop_date column in triggered table.
-
-currabstime() may be used in DEFAULT for start_date column to get
-current date.
+   You have to create trigger BEFORE (!!!) INSERT OR UPDATE OR DELETE using
+this function on a time-traveled table. You have to specify two arguments:
+name of start_date column and name of stop_date column in triggered table.
 
 set_timetravel() allows you turn time-travel ON/OFF for a table:
 
@@ -96,9 +99,26 @@ set_timetravel() allows you turn time-travel ON/OFF for a table:
 old status).
    set_timetravel('XXX', 0) will turn TT OFF for table XXX (-"-).
 
-Turning TT OFF allows you do with a table ALL what you want.
+Turning TT OFF allows you do with a table ALL what you want!
 
    There is example in timetravel.example.
 
    To CREATE FUNCTIONs use timetravel.sql (will be made by gmake from
 timetravel.source).
+
+
+3. autoinc.c - function for implementing AUTOINCREMENT/IDENTITY feature.
+
+   You have to create BEFORE INSERT OR UPDATE trigger using function
+autoinc(). You have to specify as function arguments: column name
+(of int4 type) for which you want to get this feature and name of
+SEQUENCE from which next value has to be fetched when NULL or 0
+value is being inserted into column (, ... - you are able to specify
+as many column/sequence pairs as you need).
+
+   There is example in autoinc.example.
+
+   To CREATE FUNCTION use autoinc.sql (will be made by gmake from
+autoinc.source).
+
+
diff --git a/contrib/spi/autoinc.c b/contrib/spi/autoinc.c
new file mode 100644
index 0000000000..2aceea60fa
--- /dev/null
+++ b/contrib/spi/autoinc.c
@@ -0,0 +1,100 @@
+
+#include "executor/spi.h"		/* this is what you need to work with SPI */
+#include "commands/trigger.h"	/* -"- and triggers */
+
+HeapTuple		autoinc(void);
+
+extern int4	nextval(struct varlena * seqin);
+
+HeapTuple
+autoinc()
+{
+	Trigger    *trigger;		/* to get trigger name */
+	int			nargs;			/* # of arguments */
+	int		   *chattrs;		/* attnums of attributes to change */
+	int			chnattrs = 0;	/* # of above */
+	Datum	   *newvals;		/* vals of above */
+	char	  **args;			/* arguments */
+	char	   *relname;		/* triggered relation name */
+	Relation	rel;			/* triggered relation */
+	HeapTuple	rettuple = NULL;
+	TupleDesc	tupdesc;		/* tuple description */
+	bool		isnull;
+	int			i;
+
+	if (!CurrentTriggerData)
+		elog(WARN, "autoinc: triggers are not initialized");
+	if (TRIGGER_FIRED_FOR_STATEMENT(CurrentTriggerData->tg_event))
+		elog(WARN, "autoinc: can't process STATEMENT events");
+	if (TRIGGER_FIRED_AFTER(CurrentTriggerData->tg_event))
+		elog(WARN, "autoinc: must be fired before event");
+	
+	if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
+		rettuple = CurrentTriggerData->tg_trigtuple;
+	else if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event))
+		rettuple = CurrentTriggerData->tg_newtuple;
+	else
+		elog(WARN, "autoinc: can't process DELETE events");
+	
+	rel = CurrentTriggerData->tg_relation;
+	relname = SPI_getrelname(rel);
+	
+	trigger = CurrentTriggerData->tg_trigger;
+
+	nargs = trigger->tgnargs;
+	if (nargs <= 0 || nargs % 2 != 0)
+		elog(WARN, "autoinc (%s): even number gt 0 of arguments was expected", relname);
+	
+	args = trigger->tgargs;
+	tupdesc = rel->rd_att;
+	
+	CurrentTriggerData = NULL;
+	
+	chattrs = (int *) palloc (nargs/2 * sizeof (int));
+	newvals = (Datum *) palloc (nargs/2 * sizeof (Datum));
+	
+	for (i = 0; i < nargs; )
+	{
+		struct varlena	   *seqname;
+		int					attnum = SPI_fnumber (tupdesc, args[i]);
+		int32				val;
+		
+		if ( attnum < 0 )
+			elog(WARN, "autoinc (%s): there is no attribute %s", relname, args[i]);
+		if (SPI_gettypeid (tupdesc, attnum) != INT4OID)
+			elog(WARN, "autoinc (%s): attribute %s must be of INT4 type", 
+					relname, args[i]);
+		
+		val = DatumGetInt32 (SPI_getbinval (rettuple, tupdesc, attnum, &isnull));
+		
+		if (!isnull && val != 0)
+		{
+			i += 2;
+			continue;
+		}
+		
+		i++;
+		chattrs[chnattrs] = attnum;
+		seqname = textin (args[i]);
+		newvals[chnattrs] = Int32GetDatum (nextval (seqname));
+		if ( DatumGetInt32 (newvals[chnattrs]) == 0 )
+			newvals[chnattrs] = Int32GetDatum (nextval (seqname));
+		pfree (seqname);
+		chnattrs++;
+		i++;
+	}
+	
+	if (chnattrs > 0)
+	{
+		rettuple = SPI_modifytuple (rel, rettuple, chnattrs, chattrs, newvals, NULL);
+		if ( rettuple == NULL )
+			elog (WARN, "autoinc (%s): %d returned by SPI_modifytuple",
+				relname, SPI_result);
+	}
+	
+	pfree (relname);
+	pfree (chattrs);
+	pfree (newvals);
+
+	return (rettuple);
+}
diff --git a/contrib/spi/autoinc.example b/contrib/spi/autoinc.example
new file mode 100644
index 0000000000..a2f470dc2d
--- /dev/null
+++ b/contrib/spi/autoinc.example
@@ -0,0 +1,35 @@
+DROP SEQUENCE next_id;
+DROP TABLE ids;
+
+CREATE SEQUENCE next_id START -2 MINVALUE -2;
+
+CREATE TABLE ids (
+	id		int4,
+	idesc		text
+);
+
+CREATE TRIGGER ids_nextid 
+	BEFORE INSERT OR UPDATE ON ids
+	FOR EACH ROW 
+	EXECUTE PROCEDURE autoinc (id, next_id);
+
+INSERT INTO ids VALUES (0, 'first (-2 ?)');
+INSERT INTO ids VALUES (null, 'second (-1 ?)');
+INSERT INTO ids(idesc) VALUES ('third (1 ?!)');
+
+SELECT * FROM ids;
+
+UPDATE ids SET id = null, idesc = 'first: -2 --> 2' 
+	WHERE idesc = 'first (-2 ?)';
+UPDATE ids SET id = 0, idesc = 'second: -1 --> 3' 
+	WHERE id = -1;
+UPDATE ids SET id = 4, idesc = 'third: 1 --> 4' 
+	WHERE id = 1;
+
+SELECT * FROM ids;
+
+SELECT 'Wasn''t it 4 ?' as nextval, nextval ('next_id') as value;
+
+insert into ids (idesc) select textcat (idesc, '. Copy.') from ids;
+
+SELECT * FROM ids;
diff --git a/contrib/spi/autoinc.source b/contrib/spi/autoinc.source
new file mode 100644
index 0000000000..ff7dec87c6
--- /dev/null
+++ b/contrib/spi/autoinc.source
@@ -0,0 +1,6 @@
+DROP FUNCTION autoinc();
+
+CREATE FUNCTION autoinc() 
+	RETURNS opaque 
+	AS '_OBJWD_/autoinc_DLSUFFIX_'
+	LANGUAGE 'c';
diff --git a/contrib/spi/timetravel.c b/contrib/spi/timetravel.c
index dcabb351f4..db30e6f6d9 100644
--- a/contrib/spi/timetravel.c
+++ b/contrib/spi/timetravel.c
@@ -38,6 +38,8 @@ static EPlan *find_plan(char *ident, EPlan ** eplan, int *nplans);
  * 		2.	IF an delete affects tuple with stop_date eq INFINITY
  * 			then insert the same tuple with stop_date eq current date
  * 			ELSE - skip deletion of tuple.
+ * 		3.	On INSERT, if start_date is NULL then current date will be
+ * 			inserted, if stop_date is NULL then INFINITY will be inserted.
  * 
  * In CREATE TRIGGER you are to specify start_date and stop_date column
  * names:
@@ -65,6 +67,7 @@ timetravel()
 	EPlan	   *plan;			/* prepared plan */
 	char		ident[2 * NAMEDATALEN];
 	bool		isnull;			/* to know is some column NULL or not */
+	bool		isinsert = false;
 	int			ret;
 	int			i;
 
@@ -86,7 +89,7 @@ timetravel()
 
 	/* INSERT ? */
 	if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
-		elog (WARN, "timetravel: can't process INSERT event");
+		isinsert = true;
 	
 	if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event))
 		newtuple = CurrentTriggerData->tg_newtuple;
@@ -133,6 +136,50 @@ timetravel()
 					relname, args[0], args[1]);
 	}
 	
+	if (isinsert)								/* INSERT */
+	{
+		int		chnattrs = 0;
+		int		chattrs[2];
+		Datum	newvals[2];
+		
+		oldon = SPI_getbinval (trigtuple, tupdesc, attnum[0], &isnull);
+		if (isnull)
+		{
+			newvals[chnattrs] = GetCurrentAbsoluteTime ();
+			chattrs[chnattrs] = attnum[0];
+			chnattrs++;
+		}
+		
+		oldoff = SPI_getbinval (trigtuple, tupdesc, attnum[1], &isnull);
+		if (isnull)
+		{
+			if ((chnattrs == 0 && DatumGetInt32 (oldon) >= NOEND_ABSTIME) || 
+				(chnattrs > 0 && DatumGetInt32 (newvals[0]) >= NOEND_ABSTIME))
+				elog (WARN, "timetravel (%s): %s ge %s", 
+						relname, args[0], args[1]);
+			newvals[chnattrs] = NOEND_ABSTIME;
+			chattrs[chnattrs] = attnum[1];
+			chnattrs++;
+		}
+		else
+		{
+			if ((chnattrs == 0 && DatumGetInt32 (oldon) >= 
+										DatumGetInt32 (oldoff)) || 
+				(chnattrs > 0 && DatumGetInt32 (newvals[0]) >= 
+										DatumGetInt32 (oldoff)))
+				elog (WARN, "timetravel (%s): %s ge %s", 
+						relname, args[0], args[1]);
+		}
+		
+		pfree (relname);
+		if ( chnattrs <= 0 )
+			return (trigtuple);
+		
+		rettuple = SPI_modifytuple (rel, trigtuple, chnattrs, 
+									chattrs, newvals, NULL);
+		return (rettuple);
+	}
+	
 	oldon = SPI_getbinval (trigtuple, tupdesc, attnum[0], &isnull);
 	if (isnull)
 		elog(WARN, "timetravel (%s): %s must be NOT NULL", relname, args[0]);
@@ -140,7 +187,6 @@ timetravel()
 	oldoff = SPI_getbinval (trigtuple, tupdesc, attnum[1], &isnull);
 	if (isnull)
 		elog(WARN, "timetravel (%s): %s must be NOT NULL", relname, args[1]);
-	
 	/*
 	 * If DELETE/UPDATE of tuple with stop_date neq INFINITY
 	 * then say upper Executor to skip operation for this tuple
diff --git a/contrib/spi/timetravel.example b/contrib/spi/timetravel.example
index 00cb30120c..fa76c25017 100644
--- a/contrib/spi/timetravel.example
+++ b/contrib/spi/timetravel.example
@@ -1,21 +1,26 @@
 drop table tttest;
+
 create table tttest (
 	price_id	int4, 
 	price_val	int4, 
-	price_on	abstime default currabstime(),
-	price_off	abstime default 'infinity'
+	price_on	abstime,
+	price_off	abstime
 );
 
-insert into tttest values (1, 1, null, null);
-insert into tttest values (2, 2, null, null);
-insert into tttest values (3, 3, null, null);
-
 create trigger timetravel 
-	before delete or update on tttest
+	before insert or delete or update on tttest
 	for each row 
 	execute procedure 
 	timetravel (price_on, price_off);
 
+insert into tttest values (1, 1, null, null);
+insert into tttest(price_id, price_val) values (2, 2);
+insert into tttest(price_id, price_val,price_off) values (3, 3, 'infinity');
+
+insert into tttest(price_id, price_val,price_off) values (3, 3, 
+	datetime_abstime(datetime_mi_span('now', '100')));
+insert into tttest(price_id, price_val,price_on) values (3, 3, 'infinity');
+
 select * from tttest;
 delete from tttest where price_id = 2;
 select * from tttest;
diff --git a/contrib/spi/timetravel.source b/contrib/spi/timetravel.source
index 8a1e1e17f6..5de63d3821 100644
--- a/contrib/spi/timetravel.source
+++ b/contrib/spi/timetravel.source
@@ -1,12 +1,6 @@
-DROP FUNCTION currabstime();
 DROP FUNCTION timetravel();
 DROP FUNCTION set_timetravel(name, int4);
 
-CREATE FUNCTION currabstime() 
-	RETURNS abstime 
-	AS '_OBJWD_/timetravel_DLSUFFIX_'
-	LANGUAGE 'c';
-
 CREATE FUNCTION timetravel() 
 	RETURNS opaque 
 	AS '_OBJWD_/timetravel_DLSUFFIX_'