OSDN Git Service

General function for SERIAL/IDENTITY/AUTOINCREMENT feature.
authorVadim B. Mikheev <vadim4o@yahoo.com>
Thu, 2 Oct 1997 18:01:57 +0000 (18:01 +0000)
committerVadim B. Mikheev <vadim4o@yahoo.com>
Thu, 2 Oct 1997 18:01:57 +0000 (18:01 +0000)
Handle INSERT event in timetravel().

contrib/spi/Makefile
contrib/spi/README
contrib/spi/autoinc.c [new file with mode: 0644]
contrib/spi/autoinc.example [new file with mode: 0644]
contrib/spi/autoinc.source [new file with mode: 0644]
contrib/spi/timetravel.c
contrib/spi/timetravel.example
contrib/spi/timetravel.source

index 3f5790a..7082762 100644 (file)
@@ -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)
 
index 65868f0..5d332cb 100644 (file)
@@ -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 (file)
index 0000000..2aceea6
--- /dev/null
@@ -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 (file)
index 0000000..a2f470d
--- /dev/null
@@ -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 (file)
index 0000000..ff7dec8
--- /dev/null
@@ -0,0 +1,6 @@
+DROP FUNCTION autoinc();
+
+CREATE FUNCTION autoinc() 
+       RETURNS opaque 
+       AS '_OBJWD_/autoinc_DLSUFFIX_'
+       LANGUAGE 'c';
index dcabb35..db30e6f 100644 (file)
@@ -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
index 00cb301..fa76c25 100644 (file)
@@ -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;
index 8a1e1e1..5de63d3 100644 (file)
@@ -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_'