Repair RI trigger visibility problems (this time for sure ;-)) per recent
authorTom Lane
Wed, 1 Oct 2003 21:30:53 +0000 (21:30 +0000)
committerTom Lane
Wed, 1 Oct 2003 21:30:53 +0000 (21:30 +0000)
discussion on pgsql-hackers: in READ COMMITTED mode we just have to force
a QuerySnapshot update in the trigger, but in SERIALIZABLE mode we have
to run the scan under a current snapshot and then complain if any rows
would be updated/deleted that are not visible in the transaction snapshot.

15 files changed:
src/backend/access/heap/heapam.c
src/backend/commands/async.c
src/backend/executor/execMain.c
src/backend/executor/execUtils.c
src/backend/executor/nodeSubplan.c
src/backend/executor/nodeSubqueryscan.c
src/backend/executor/spi.c
src/backend/storage/ipc/sinval.c
src/backend/utils/adt/ri_triggers.c
src/backend/utils/time/tqual.c
src/include/access/heapam.h
src/include/executor/executor.h
src/include/executor/spi.h
src/include/nodes/execnodes.h
src/include/utils/tqual.h

index 2ddab234b0a104e6dd459fd8f431c720fc6ef4ce..1d1bd6f63718ae3e557601e95d40f083781717cf 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/access/heap/heapam.c,v 1.156 2003/09/25 06:57:56 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/access/heap/heapam.c,v 1.157 2003/10/01 21:30:52 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1207,14 +1207,23 @@ simple_heap_insert(Relation relation, HeapTuple tup)
  * NB: do not call this directly unless you are prepared to deal with
  * concurrent-update conditions.  Use simple_heap_delete instead.
  *
+ * relation - table to be modified
+ * tid - TID of tuple to be deleted
+ * ctid - output parameter, used only for failure case (see below)
+ * cid - delete command ID to use in verifying tuple visibility
+ * crosscheck - if not SnapshotAny, also check tuple against this
+ * wait - true if should wait for any conflicting update to commit/abort
+ *
  * Normal, successful return value is HeapTupleMayBeUpdated, which
  * actually means we did delete it.  Failure return codes are
  * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
- * (the last only possible if wait == false).
+ * (the last only possible if wait == false).  On a failure return,
+ * *ctid is set to the ctid link of the target tuple (possibly a later
+ * version of the row).
  */
 int
 heap_delete(Relation relation, ItemPointer tid,
-           ItemPointer ctid, CommandId cid, bool wait)
+           ItemPointer ctid, CommandId cid, Snapshot crosscheck, bool wait)
 {
    ItemId      lp;
    HeapTupleData tp;
@@ -1240,7 +1249,7 @@ heap_delete(Relation relation, ItemPointer tid,
    tp.t_tableOid = relation->rd_id;
 
 l1:
-   result = HeapTupleSatisfiesUpdate(&tp, cid);
+   result = HeapTupleSatisfiesUpdate(tp.t_data, cid);
 
    if (result == HeapTupleInvisible)
    {
@@ -1278,6 +1287,14 @@ l1:
        else
            result = HeapTupleUpdated;
    }
+
+   if (crosscheck != SnapshotAny && result == HeapTupleMayBeUpdated)
+   {
+       /* Perform additional check for serializable RI updates */
+       if (!HeapTupleSatisfiesSnapshot(tp.t_data, crosscheck))
+           result = HeapTupleUpdated;
+   }
+
    if (result != HeapTupleMayBeUpdated)
    {
        Assert(result == HeapTupleSelfUpdated ||
@@ -1378,7 +1395,7 @@ simple_heap_delete(Relation relation, ItemPointer tid)
 
    result = heap_delete(relation, tid,
                         &ctid,
-                        GetCurrentCommandId(),
+                        GetCurrentCommandId(), SnapshotAny,
                         true /* wait for commit */);
    switch (result)
    {
@@ -1407,14 +1424,26 @@ simple_heap_delete(Relation relation, ItemPointer tid)
  * NB: do not call this directly unless you are prepared to deal with
  * concurrent-update conditions.  Use simple_heap_update instead.
  *
+ * relation - table to be modified
+ * otid - TID of old tuple to be replaced
+ * newtup - newly constructed tuple data to store
+ * ctid - output parameter, used only for failure case (see below)
+ * cid - update command ID to use in verifying old tuple visibility
+ * crosscheck - if not SnapshotAny, also check old tuple against this
+ * wait - true if should wait for any conflicting update to commit/abort
+ *
  * Normal, successful return value is HeapTupleMayBeUpdated, which
  * actually means we *did* update it.  Failure return codes are
  * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
- * (the last only possible if wait == false).
+ * (the last only possible if wait == false).  On a failure return,
+ * *ctid is set to the ctid link of the old tuple (possibly a later
+ * version of the row).
+ * On success, newtup->t_self is set to the TID where the new tuple
+ * was inserted.
  */
 int
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
-           ItemPointer ctid, CommandId cid, bool wait)
+           ItemPointer ctid, CommandId cid, Snapshot crosscheck, bool wait)
 {
    ItemId      lp;
    HeapTupleData oldtup;
@@ -1450,7 +1479,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
     */
 
 l2:
-   result = HeapTupleSatisfiesUpdate(&oldtup, cid);
+   result = HeapTupleSatisfiesUpdate(oldtup.t_data, cid);
 
    if (result == HeapTupleInvisible)
    {
@@ -1488,6 +1517,14 @@ l2:
        else
            result = HeapTupleUpdated;
    }
+
+   if (crosscheck != SnapshotAny && result == HeapTupleMayBeUpdated)
+   {
+       /* Perform additional check for serializable RI updates */
+       if (!HeapTupleSatisfiesSnapshot(oldtup.t_data, crosscheck))
+           result = HeapTupleUpdated;
+   }
+
    if (result != HeapTupleMayBeUpdated)
    {
        Assert(result == HeapTupleSelfUpdated ||
@@ -1718,7 +1755,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 
    result = heap_update(relation, otid, tup,
                         &ctid,
-                        GetCurrentCommandId(),
+                        GetCurrentCommandId(), SnapshotAny,
                         true /* wait for commit */);
    switch (result)
    {
@@ -1767,7 +1804,7 @@ heap_mark4update(Relation relation, HeapTuple tuple, Buffer *buffer,
    tuple->t_len = ItemIdGetLength(lp);
 
 l3:
-   result = HeapTupleSatisfiesUpdate(tuple, cid);
+   result = HeapTupleSatisfiesUpdate(tuple->t_data, cid);
 
    if (result == HeapTupleInvisible)
    {
index 95c83c2372e69cf2247d27b20190d053d488edc8..d977995f5ff084a0c701010f922ac397ae7769ca 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.100 2003/09/15 23:33:39 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.101 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -537,7 +537,7 @@ AtCommit_Notify(void)
                 */
                result = heap_update(lRel, &lTuple->t_self, rTuple,
                                     &ctid,
-                                    GetCurrentCommandId(),
+                                    GetCurrentCommandId(), SnapshotAny,
                                     false /* no wait for commit */);
                switch (result)
                {
index 6b92920bcd1686f9db1564b8f4a8dada687bc990..bfdc94c6d5169ddfaff7bbca8013686f949dc119 100644 (file)
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.219 2003/09/25 18:58:35 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.220 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -104,8 +104,14 @@ static void EvalPlanQualStop(evalPlanQual *epq);
  * field of the QueryDesc is filled in to describe the tuples that will be
  * returned, and the internal fields (estate and planstate) are set up.
  *
- * If useSnapshotNow is true, run the query with SnapshotNow time qual rules
- * instead of the normal use of QuerySnapshot.
+ * If useCurrentSnapshot is true, run the query with the latest available
+ * snapshot, instead of the normal QuerySnapshot.  Also, if it's an update
+ * or delete query, check that the rows to be updated or deleted would be
+ * visible to the normal QuerySnapshot.  (This is a special-case behavior
+ * needed for referential integrity updates in serializable transactions.
+ * We must check all currently-committed rows, but we want to throw a
+ * can't-serialize error if any rows that would need updates would not be
+ * visible under the normal serializable snapshot.)
  *
  * If explainOnly is true, we are not actually intending to run the plan,
  * only to set up for EXPLAIN; so skip unwanted side-effects.
@@ -115,7 +121,7 @@ static void EvalPlanQualStop(evalPlanQual *epq);
  * ----------------------------------------------------------------
  */
 void
-ExecutorStart(QueryDesc *queryDesc, bool useSnapshotNow, bool explainOnly)
+ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot, bool explainOnly)
 {
    EState     *estate;
    MemoryContext oldcontext;
@@ -157,15 +163,18 @@ ExecutorStart(QueryDesc *queryDesc, bool useSnapshotNow, bool explainOnly)
     * the life of this query, even if it outlives the current command and
     * current snapshot.
     */
-   if (useSnapshotNow)
+   if (useCurrentSnapshot)
    {
-       estate->es_snapshot = SnapshotNow;
-       estate->es_snapshot_cid = GetCurrentCommandId();
+       /* RI update/delete query --- must use an up-to-date snapshot */
+       estate->es_snapshot = CopyCurrentSnapshot();
+       /* crosscheck updates/deletes against transaction snapshot */
+       estate->es_crosscheck_snapshot = CopyQuerySnapshot();
    }
    else
    {
+       /* normal query --- use query snapshot, no crosscheck */
        estate->es_snapshot = CopyQuerySnapshot();
-       estate->es_snapshot_cid = estate->es_snapshot->curcid;
+       estate->es_crosscheck_snapshot = SnapshotAny;
    }
 
    /*
@@ -1118,7 +1127,7 @@ lnext:    ;
 
                    tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
                    test = heap_mark4update(erm->relation, &tuple, &buffer,
-                                           estate->es_snapshot_cid);
+                                           estate->es_snapshot->curcid);
                    ReleaseBuffer(buffer);
                    switch (test)
                    {
@@ -1278,7 +1287,7 @@ ExecSelect(TupleTableSlot *slot,
    if (estate->es_into_relation_descriptor != NULL)
    {
        heap_insert(estate->es_into_relation_descriptor, tuple,
-                   estate->es_snapshot_cid);
+                   estate->es_snapshot->curcid);
        IncrAppended();
    }
 
@@ -1354,7 +1363,7 @@ ExecInsert(TupleTableSlot *slot,
     * insert the tuple
     */
    newId = heap_insert(resultRelationDesc, tuple,
-                       estate->es_snapshot_cid);
+                       estate->es_snapshot->curcid);
 
    IncrAppended();
    (estate->es_processed)++;
@@ -1406,7 +1415,7 @@ ExecDelete(TupleTableSlot *slot,
        bool        dodelete;
 
        dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid,
-                                       estate->es_snapshot_cid);
+                                       estate->es_snapshot->curcid);
 
        if (!dodelete)          /* "do nothing" */
            return;
@@ -1418,7 +1427,8 @@ ExecDelete(TupleTableSlot *slot,
 ldelete:;
    result = heap_delete(resultRelationDesc, tupleid,
                         &ctid,
-                        estate->es_snapshot_cid,
+                        estate->es_snapshot->curcid,
+                        estate->es_crosscheck_snapshot,
                         true /* wait for commit */);
    switch (result)
    {
@@ -1517,7 +1527,7 @@ ExecUpdate(TupleTableSlot *slot,
 
        newtuple = ExecBRUpdateTriggers(estate, resultRelInfo,
                                        tupleid, tuple,
-                                       estate->es_snapshot_cid);
+                                       estate->es_snapshot->curcid);
 
        if (newtuple == NULL)   /* "do nothing" */
            return;
@@ -1553,7 +1563,8 @@ lreplace:;
     */
    result = heap_update(resultRelationDesc, tupleid, tuple,
                         &ctid,
-                        estate->es_snapshot_cid,
+                        estate->es_snapshot->curcid,
+                        estate->es_crosscheck_snapshot,
                         true /* wait for commit */);
    switch (result)
    {
@@ -2039,7 +2050,7 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq)
     */
    epqstate->es_direction = ForwardScanDirection;
    epqstate->es_snapshot = estate->es_snapshot;
-   epqstate->es_snapshot_cid = estate->es_snapshot_cid;
+   epqstate->es_crosscheck_snapshot = estate->es_crosscheck_snapshot;
    epqstate->es_range_table = estate->es_range_table;
    epqstate->es_result_relations = estate->es_result_relations;
    epqstate->es_num_result_relations = estate->es_num_result_relations;
index c9c7ef79396b26190b4ba95deeb8d5f4c507a991..1ee99cb359ec0792491886fe81081b9389d374da 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.105 2003/09/25 18:58:35 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.106 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -178,7 +178,7 @@ CreateExecutorState(void)
     */
    estate->es_direction = ForwardScanDirection;
    estate->es_snapshot = SnapshotNow;
-   estate->es_snapshot_cid = FirstCommandId;
+   estate->es_crosscheck_snapshot = SnapshotAny; /* means no crosscheck */
    estate->es_range_table = NIL;
 
    estate->es_result_relations = NULL;
index 488a37b24d3f539190d94dc61e4184977dec0440..971dd5879db071f5ecccb80860e43204a70c254e 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.57 2003/09/25 18:58:35 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.58 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -709,7 +709,7 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
    sp_estate->es_tupleTable =
        ExecCreateTupleTable(ExecCountSlotsNode(subplan->plan) + 10);
    sp_estate->es_snapshot = estate->es_snapshot;
-   sp_estate->es_snapshot_cid = estate->es_snapshot_cid;
+   sp_estate->es_crosscheck_snapshot = estate->es_crosscheck_snapshot;
    sp_estate->es_instrument = estate->es_instrument;
 
    /*
index f8d2640349f81e829b4d104c6578e05ef2220098..1c15f5ff39135fa5266f9d5adf85915912f71db6 100644 (file)
@@ -12,7 +12,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.21 2003/09/25 18:58:35 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.22 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -177,7 +177,7 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate)
    sp_estate->es_tupleTable =
        ExecCreateTupleTable(ExecCountSlotsNode(node->subplan) + 10);
    sp_estate->es_snapshot = estate->es_snapshot;
-   sp_estate->es_snapshot_cid = estate->es_snapshot_cid;
+   sp_estate->es_crosscheck_snapshot = estate->es_crosscheck_snapshot;
    sp_estate->es_instrument = estate->es_instrument;
 
    /*
index 2626b728e94131c35156e8bcdf3f1ff88bf7009c..e9e2084fdaf157f305894b52982fedf3eea0f288 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.106 2003/09/25 18:58:35 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.107 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -33,11 +33,11 @@ static int  _SPI_curid = -1;
 
 static int _SPI_execute(const char *src, int tcount, _SPI_plan *plan);
 static int _SPI_pquery(QueryDesc *queryDesc, bool runit,
-                       bool useSnapshotNow, int tcount);
+                       bool useCurrentSnapshot, int tcount);
 
 static int _SPI_execute_plan(_SPI_plan *plan,
                             Datum *Values, const char *Nulls,
-                            bool useSnapshotNow, int tcount);
+                            bool useCurrentSnapshot, int tcount);
 
 static void _SPI_cursor_operation(Portal portal, bool forward, int count,
                      DestReceiver *dest);
@@ -245,12 +245,14 @@ SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
 }
 
 /*
- * SPI_execp_now -- identical to SPI_execp, except that we use SnapshotNow
- * instead of the normal QuerySnapshot.  This is currently not documented
- * in spi.sgml because it is only intended for use by RI triggers.
+ * SPI_execp_current -- identical to SPI_execp, except that we expose the
+ * Executor option to use a current snapshot instead of the normal
+ * QuerySnapshot.  This is currently not documented in spi.sgml because
+ * it is only intended for use by RI triggers.
  */
 int
-SPI_execp_now(void *plan, Datum *Values, const char *Nulls, int tcount)
+SPI_execp_current(void *plan, Datum *Values, const char *Nulls,
+                 bool useCurrentSnapshot, int tcount)
 {
    int         res;
 
@@ -264,7 +266,8 @@ SPI_execp_now(void *plan, Datum *Values, const char *Nulls, int tcount)
    if (res < 0)
        return res;
 
-   res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls, true, tcount);
+   res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls,
+                           useCurrentSnapshot, tcount);
 
    _SPI_end_call(true);
    return res;
@@ -1124,7 +1127,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
 
 static int
 _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
-                 bool useSnapshotNow, int tcount)
+                 bool useCurrentSnapshot, int tcount)
 {
    List       *query_list_list = plan->qtlist;
    List       *plan_list = plan->ptlist;
@@ -1195,7 +1198,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
            {
                qdesc = CreateQueryDesc(queryTree, planTree, dest,
                                        paramLI, false);
-               res = _SPI_pquery(qdesc, true, useSnapshotNow,
+               res = _SPI_pquery(qdesc, true, useCurrentSnapshot,
                                  queryTree->canSetTag ? tcount : 0);
                if (res < 0)
                    return res;
@@ -1208,7 +1211,8 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 }
 
 static int
-_SPI_pquery(QueryDesc *queryDesc, bool runit, bool useSnapshotNow, int tcount)
+_SPI_pquery(QueryDesc *queryDesc, bool runit,
+           bool useCurrentSnapshot, int tcount)
 {
    int         operation = queryDesc->operation;
    int         res;
@@ -1245,7 +1249,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit, bool useSnapshotNow, int tcount)
        ResetUsage();
 #endif
 
-   ExecutorStart(queryDesc, useSnapshotNow, false);
+   ExecutorStart(queryDesc, useCurrentSnapshot, false);
 
    ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
 
index 366a606684ae1e95e0098a08ff1fb5f732a52a32..19a9093f8728078e2d4bf958ca8c9d9dc3628553 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.60 2003/09/24 18:54:01 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.61 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -330,10 +330,10 @@ GetSnapshotData(Snapshot snapshot, bool serializable)
     * lastBackend would be sufficient.  But it seems better to do the
     * malloc while not holding the lock, so we can't look at lastBackend.
     *
-    * if (snapshot->xip != NULL) no need to free and reallocate xip;
-    *
-    * We can reuse the old xip array, because MaxBackends does not change at
-    * runtime.
+    * This does open a possibility for avoiding repeated malloc/free:
+    * since MaxBackends does not change at runtime, we can simply reuse
+    * the previous xip array if any.  (This relies on the fact that all
+    * calls pass static SnapshotData structs.)
     */
    if (snapshot->xip == NULL)
    {
index 181484b7b2d547c1e5069d0a1f1a389aef461661..11b7e84df03767bcc12556163bb3c5d99312a67e 100644 (file)
@@ -17,7 +17,7 @@
  *
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.60 2003/09/29 00:05:25 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.61 2003/10/01 21:30:52 tgl Exp $
  *
  * ----------
  */
@@ -157,6 +157,7 @@ static void *ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 static bool ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
                Relation fk_rel, Relation pk_rel,
                HeapTuple old_tuple, HeapTuple new_tuple,
+               bool detectNewRows,
                int expect_OK, const char *constrname);
 static void ri_ExtractValues(RI_QueryKey *qkey, int key_idx,
                 Relation rel, HeapTuple tuple,
@@ -276,6 +277,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
        ri_PerformCheck(&qkey, qplan,
                        fk_rel, pk_rel,
                        NULL, NULL,
+                       false,
                        SPI_OK_SELECT,
                        tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -433,6 +435,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
    ri_PerformCheck(&qkey, qplan,
                    fk_rel, pk_rel,
                    NULL, new_row,
+                   false,
                    SPI_OK_SELECT,
                    tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -594,6 +597,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
    result = ri_PerformCheck(&qkey, qplan,
                             fk_rel, pk_rel,
                             old_row, NULL,
+                            true,      /* treat like update */
                             SPI_OK_SELECT, NULL);
 
    if (SPI_finish() != SPI_OK_FINISH)
@@ -752,6 +756,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_SELECT,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -942,6 +947,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_SELECT,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -1102,6 +1108,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_DELETE,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -1285,6 +1292,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, new_row,
+                           true, /* must detect new rows */
                            SPI_OK_UPDATE,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -1453,6 +1461,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_SELECT,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -1633,6 +1642,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_SELECT,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -1802,6 +1812,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_UPDATE,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -2019,6 +2030,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_UPDATE,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -2188,6 +2200,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_UPDATE,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -2392,6 +2405,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
            ri_PerformCheck(&qkey, qplan,
                            fk_rel, pk_rel,
                            old_row, NULL,
+                           true, /* must detect new rows */
                            SPI_OK_UPDATE,
                            tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
@@ -2788,11 +2802,13 @@ static bool
 ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
                Relation fk_rel, Relation pk_rel,
                HeapTuple old_tuple, HeapTuple new_tuple,
+               bool detectNewRows,
                int expect_OK, const char *constrname)
 {
    Relation    query_rel,
                source_rel;
    int         key_idx;
+   bool        useCurrentSnapshot;
    int         limit;
    int         spi_result;
    AclId       save_uid;
@@ -2842,9 +2858,25 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
                         vals, nulls);
    }
 
-   /* Switch to proper UID to perform check as */
-   save_uid = GetUserId();
-   SetUserId(RelationGetForm(query_rel)->relowner);
+   /*
+    * In READ COMMITTED mode, we just need to make sure the regular query
+    * snapshot is up-to-date, and we will see all rows that could be
+    * interesting.  In SERIALIZABLE mode, we can't update the regular query
+    * snapshot.  If the caller passes detectNewRows == false then it's okay
+    * to do the query with the transaction snapshot; otherwise we tell the
+    * executor to force a current snapshot (and error out if it finds any
+    * rows under current snapshot that wouldn't be visible per the
+    * transaction snapshot).
+    */
+   if (XactIsoLevel == XACT_SERIALIZABLE)
+   {
+       useCurrentSnapshot = detectNewRows;
+   }
+   else
+   {
+       SetQuerySnapshot();
+       useCurrentSnapshot = false;
+   }
 
    /*
     * If this is a select query (e.g., for a 'no action' or 'restrict'
@@ -2854,19 +2886,20 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
     */
    limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0;
 
-   /*
-    * Run the plan, using SnapshotNow time qual rules so that we can see
-    * all committed tuples, even those committed after our own transaction
-    * or query started.
-    */
-   spi_result = SPI_execp_now(qplan, vals, nulls, limit);
+   /* Switch to proper UID to perform check as */
+   save_uid = GetUserId();
+   SetUserId(RelationGetForm(query_rel)->relowner);
+
+   /* Finally we can run the query. */
+   spi_result = SPI_execp_current(qplan, vals, nulls,
+                                  useCurrentSnapshot, limit);
 
    /* Restore UID */
    SetUserId(save_uid);
 
    /* Check result */
    if (spi_result < 0)
-       elog(ERROR, "SPI_execp_now returned %d", spi_result);
+       elog(ERROR, "SPI_execp_current returned %d", spi_result);
 
    if (expect_OK >= 0 && spi_result != expect_OK)
        ri_ReportViolation(qkey, constrname ? constrname : "",
index 5b594fcf6801057ca804db83b47da20b41d41942..d7d22b77866878b6634347a30ed78ed855a0c117 100644 (file)
@@ -16,7 +16,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/time/tqual.c,v 1.69 2003/09/25 18:58:35 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/time/tqual.c,v 1.70 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "storage/sinval.h"
 #include "utils/tqual.h"
 
-
-static SnapshotData SnapshotDirtyData;
-Snapshot   SnapshotDirty = &SnapshotDirtyData;
-
+/*
+ * The SnapshotData structs are static to simplify memory allocation
+ * (see the hack in GetSnapshotData to avoid repeated malloc/free).
+ */
 static SnapshotData QuerySnapshotData;
 static SnapshotData SerializableSnapshotData;
+static SnapshotData CurrentSnapshotData;
+static SnapshotData SnapshotDirtyData;
+
+/* Externally visible pointers to valid snapshots: */
 Snapshot   QuerySnapshot = NULL;
 Snapshot   SerializableSnapshot = NULL;
+Snapshot   SnapshotDirty = &SnapshotDirtyData;
 
 /* These are updated by GetSnapshotData: */
 TransactionId RecentXmin = InvalidTransactionId;
@@ -387,10 +392,8 @@ HeapTupleSatisfiesToast(HeapTupleHeader tuple)
  * CurrentCommandId.
  */
 int
-HeapTupleSatisfiesUpdate(HeapTuple htuple, CommandId curcid)
+HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid)
 {
-   HeapTupleHeader tuple = htuple->t_data;
-
    if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
    {
        if (tuple->t_infomask & HEAP_XMIN_INVALID)
@@ -1023,6 +1026,42 @@ CopyQuerySnapshot(void)
    return snapshot;
 }
 
+/*
+ * CopyCurrentSnapshot
+ *     Make a snapshot that is up-to-date as of the current instant,
+ *     and return a copy.
+ *
+ * The copy is palloc'd in the current memory context.
+ */
+Snapshot
+CopyCurrentSnapshot(void)
+{
+   Snapshot    currentSnapshot;
+   Snapshot    snapshot;
+
+   if (QuerySnapshot == NULL)  /* should not be first call in xact */
+       elog(ERROR, "no snapshot has been set");
+
+   /* Update the static struct */
+   currentSnapshot = GetSnapshotData(&CurrentSnapshotData, false);
+   currentSnapshot->curcid = GetCurrentCommandId();
+
+   /* Make a copy */
+   snapshot = (Snapshot) palloc(sizeof(SnapshotData));
+   memcpy(snapshot, currentSnapshot, sizeof(SnapshotData));
+   if (snapshot->xcnt > 0)
+   {
+       snapshot->xip = (TransactionId *)
+           palloc(snapshot->xcnt * sizeof(TransactionId));
+       memcpy(snapshot->xip, currentSnapshot->xip,
+              snapshot->xcnt * sizeof(TransactionId));
+   }
+   else
+       snapshot->xip = NULL;
+
+   return snapshot;
+}
+
 /*
  * FreeXactSnapshot
  *     Free snapshot(s) at end of transaction.
@@ -1031,8 +1070,9 @@ void
 FreeXactSnapshot(void)
 {
    /*
-    * We do not free(QuerySnapshot->xip); or
-    * free(SerializableSnapshot->xip); they will be reused soon
+    * We do not free the xip arrays for the snapshot structs;
+    * they will be reused soon.  So this is now just a state
+    * change to prevent outside callers from accessing the snapshots.
     */
    QuerySnapshot = NULL;
    SerializableSnapshot = NULL;
index 05801af9e1652a40423a2989cad4b3e33726da20..ed46894d3a59ce6c9254cd6a489d63cceb03b358 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: heapam.h,v 1.84 2003/09/15 23:33:43 tgl Exp $
+ * $Id: heapam.h,v 1.85 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -155,9 +155,9 @@ extern void setLastTid(const ItemPointer tid);
 
 extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid);
 extern int heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid,
-           CommandId cid, bool wait);
+           CommandId cid, Snapshot crosscheck, bool wait);
 extern int heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
-           ItemPointer ctid, CommandId cid, bool wait);
+           ItemPointer ctid, CommandId cid, Snapshot crosscheck, bool wait);
 extern int heap_mark4update(Relation relation, HeapTuple tup,
                 Buffer *userbuf, CommandId cid);
 
index 034494b844fd9594104e0e6bbdf4d0b84f9ae0fe..ad30681f1cdc59d79ec7c9a94fff29e59d1bbb4e 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.101 2003/09/25 18:58:35 tgl Exp $
+ * $Id: executor.h,v 1.102 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -85,7 +85,7 @@ extern HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot);
 /*
  * prototypes from functions in execMain.c
  */
-extern void ExecutorStart(QueryDesc *queryDesc, bool useSnapshotNow,
+extern void ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot,
                          bool explainOnly);
 extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc,
            ScanDirection direction, long count);
index 800616b56b6657151f908a3da634f67b98358c80..bc86c665cba7feebe7cd9912f2c29345b03ceab3 100644 (file)
@@ -2,7 +2,7 @@
  *
  * spi.h
  *
- * $Id: spi.h,v 1.38 2003/09/25 18:58:36 tgl Exp $
+ * $Id: spi.h,v 1.39 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -84,8 +84,8 @@ extern void SPI_pop(void);
 extern int SPI_exec(const char *src, int tcount);
 extern int SPI_execp(void *plan, Datum *values, const char *Nulls,
          int tcount);
-extern int SPI_execp_now(void *plan, Datum *values, const char *Nulls,
-         int tcount);
+extern int SPI_execp_current(void *plan, Datum *values, const char *Nulls,
+                            bool useCurrentSnapshot, int tcount);
 extern void *SPI_prepare(const char *src, int nargs, Oid *argtypes);
 extern void *SPI_saveplan(void *plan);
 extern int SPI_freeplan(void *plan);
index b40df71776592cae474a44cb5a8631ed654eafd2..4112cd49de6afe1094aee31433c568e35a4b31c6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.106 2003/09/25 18:58:36 tgl Exp $
+ * $Id: execnodes.h,v 1.107 2003/10/01 21:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -286,7 +286,7 @@ typedef struct EState
    /* Basic state for all query types: */
    ScanDirection es_direction; /* current scan direction */
    Snapshot    es_snapshot;    /* time qual to use */
-   CommandId   es_snapshot_cid;    /* CommandId component of time qual */
+   Snapshot    es_crosscheck_snapshot; /* crosscheck time qual for RI */
    List       *es_range_table; /* List of RangeTableEntrys */
 
    /* Info about target table for insert/update/delete queries: */
index 0c9f10f368cd190082a0858dfe88743a7db2dd1d..b363f89840d572e7c0408d66fae50453958ec752 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: tqual.h,v 1.47 2003/09/25 18:58:36 tgl Exp $
+ * $Id: tqual.h,v 1.48 2003/10/01 21:30:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -100,7 +100,7 @@ extern bool HeapTupleSatisfiesDirty(HeapTupleHeader tuple);
 extern bool HeapTupleSatisfiesToast(HeapTupleHeader tuple);
 extern bool HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple,
                           Snapshot snapshot);
-extern int HeapTupleSatisfiesUpdate(HeapTuple tuple,
+extern int HeapTupleSatisfiesUpdate(HeapTupleHeader tuple,
                         CommandId curcid);
 extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTupleHeader tuple,
                         TransactionId OldestXmin);
@@ -108,6 +108,7 @@ extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTupleHeader tuple,
 extern Snapshot GetSnapshotData(Snapshot snapshot, bool serializable);
 extern void SetQuerySnapshot(void);
 extern Snapshot CopyQuerySnapshot(void);
+extern Snapshot CopyCurrentSnapshot(void);
 extern void FreeXactSnapshot(void);
 
 #endif   /* TQUAL_H */