]> granicus.if.org Git - postgresql/commitdiff
Fix WHERE CURRENT OF when the referenced cursor uses an index-only scan.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 17 Mar 2018 18:59:31 +0000 (14:59 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 17 Mar 2018 18:59:31 +0000 (14:59 -0400)
"UPDATE/DELETE WHERE CURRENT OF cursor_name" failed, with an error message
like "cannot extract system attribute from virtual tuple", if the cursor
was using a index-only scan for the target table.  Fix it by digging the
current TID out of the indexscan state.

It seems likely that the same failure could occur for CustomScan plans
and perhaps some FDW plan types, so that leaving this to be treated as an
internal error with an obscure message isn't as good an idea as it first
seemed.  Hence, add a bit of heaptuple.c infrastructure to let us deliver
a more on-topic message.  I chose to make the message match what you get
for the case where execCurrentOf can't identify the target scan node at
all, "cursor "foo" is not a simply updatable scan of table "bar"".
Perhaps it should be different, but we can always adjust that later.

In the future, it might be nice to provide hooks that would let custom
scan providers and/or FDWs deal with this in other ways; but that's
not a suitable topic for a back-patchable bug fix.

It's been like this all along, so back-patch to all supported branches.

Yugo Nagata and Tom Lane

Discussion: https://postgr.es/m/20180201013349.937dfc5f.nagata@sraoss.co.jp

src/backend/access/common/heaptuple.c
src/backend/executor/execCurrent.c
src/include/executor/tuptable.h
src/test/regress/expected/portals.out
src/test/regress/sql/portals.sql

index 584a202ab51ec587b8869488265e3faea749d64b..61c0ce358593369f8a4ceb668d03f2db9a498779 100644 (file)
@@ -1365,6 +1365,32 @@ slot_attisnull(TupleTableSlot *slot, int attnum)
        return heap_attisnull(tuple, attnum);
 }
 
+/*
+ * slot_getsysattr
+ *             This function fetches a system attribute of the slot's current tuple.
+ *             Unlike slot_getattr, if the slot does not contain system attributes,
+ *             this will return false (with a NULL attribute value) instead of
+ *             throwing an error.
+ */
+bool
+slot_getsysattr(TupleTableSlot *slot, int attnum,
+                               Datum *value, bool *isnull)
+{
+       HeapTuple       tuple = slot->tts_tuple;
+
+       Assert(attnum < 0);                     /* else caller error */
+       if (tuple == NULL ||
+               tuple == &(slot->tts_minhdr))
+       {
+               /* No physical tuple, or minimal tuple, so fail */
+               *value = (Datum) 0;
+               *isnull = true;
+               return false;
+       }
+       *value = heap_getsysattr(tuple, attnum, slot->tts_tupleDescriptor, isnull);
+       return true;
+}
+
 /*
  * heap_freetuple
  */
index f00fce591321f4269d42e5303615c4b522e4bb77..a90a487c67c7c84784ddba37790b7b584d569f85 100644 (file)
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include "access/relscan.h"
 #include "access/sysattr.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
@@ -149,16 +150,13 @@ execCurrentOf(CurrentOfExpr *cexpr,
        }
        else
        {
-               ScanState  *scanstate;
-               bool            lisnull;
-               Oid                     tuple_tableoid PG_USED_FOR_ASSERTS_ONLY;
-               ItemPointer tuple_tid;
-
                /*
                 * Without FOR UPDATE, we dig through the cursor's plan to find the
                 * scan node.  Fail if it's not there or buried underneath
                 * aggregation.
                 */
+               ScanState  *scanstate;
+
                scanstate = search_plan_tree(queryDesc->planstate, table_oid);
                if (!scanstate)
                        ereport(ERROR,
@@ -183,21 +181,62 @@ execCurrentOf(CurrentOfExpr *cexpr,
                if (TupIsNull(scanstate->ss_ScanTupleSlot))
                        return false;
 
-               /* Use slot_getattr to catch any possible mistakes */
-               tuple_tableoid =
-                       DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot,
-                                                                                 TableOidAttributeNumber,
-                                                                                 &lisnull));
-               Assert(!lisnull);
-               tuple_tid = (ItemPointer)
-                       DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot,
-                                                                                SelfItemPointerAttributeNumber,
-                                                                                &lisnull));
-               Assert(!lisnull);
-
-               Assert(tuple_tableoid == table_oid);
-
-               *current_tid = *tuple_tid;
+               /*
+                * Extract TID of the scan's current row.  The mechanism for this is
+                * in principle scan-type-dependent, but for most scan types, we can
+                * just dig the TID out of the physical scan tuple.
+                */
+               if (IsA(scanstate, IndexOnlyScanState))
+               {
+                       /*
+                        * For IndexOnlyScan, the tuple stored in ss_ScanTupleSlot may be
+                        * a virtual tuple that does not have the ctid column, so we have
+                        * to get the TID from xs_ctup.t_self.
+                        */
+                       IndexScanDesc scan = ((IndexOnlyScanState *) scanstate)->ioss_ScanDesc;
+
+                       *current_tid = scan->xs_ctup.t_self;
+               }
+               else
+               {
+                       /*
+                        * Default case: try to fetch TID from the scan node's current
+                        * tuple.  As an extra cross-check, verify tableoid in the current
+                        * tuple.  If the scan hasn't provided a physical tuple, we have
+                        * to fail.
+                        */
+                       Datum           ldatum;
+                       bool            lisnull;
+                       ItemPointer tuple_tid;
+
+#ifdef USE_ASSERT_CHECKING
+                       if (!slot_getsysattr(scanstate->ss_ScanTupleSlot,
+                                                                TableOidAttributeNumber,
+                                                                &ldatum,
+                                                                &lisnull))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_CURSOR_STATE),
+                                                errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
+                                                               cursor_name, table_name)));
+                       Assert(!lisnull);
+                       Assert(DatumGetObjectId(ldatum) == table_oid);
+#endif
+
+                       if (!slot_getsysattr(scanstate->ss_ScanTupleSlot,
+                                                                SelfItemPointerAttributeNumber,
+                                                                &ldatum,
+                                                                &lisnull))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_CURSOR_STATE),
+                                                errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
+                                                               cursor_name, table_name)));
+                       Assert(!lisnull);
+                       tuple_tid = (ItemPointer) DatumGetPointer(ldatum);
+
+                       *current_tid = *tuple_tid;
+               }
+
+               Assert(ItemPointerIsValid(current_tid));
 
                return true;
        }
index 55f4cce4ee1bac27fa7ae9aef51faa8efa229529..ec541bb1f0bf2ced022a675f691c70999b32f597 100644 (file)
@@ -169,5 +169,7 @@ extern Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull);
 extern void slot_getallattrs(TupleTableSlot *slot);
 extern void slot_getsomeattrs(TupleTableSlot *slot, int attnum);
 extern bool slot_attisnull(TupleTableSlot *slot, int attnum);
+extern bool slot_getsysattr(TupleTableSlot *slot, int attnum,
+                               Datum *value, bool *isnull);
 
 #endif                                                 /* TUPTABLE_H */
index 1b8f7b69d1d9604e343d3b1fd7273b03a957a45d..048b2fc3e3a0d064fca7cc075fd7c7ce2e955368 100644 (file)
@@ -1245,6 +1245,30 @@ FETCH FROM c1;
 
 DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported
 ERROR:  WHERE CURRENT OF on a view is not implemented
+ROLLBACK;
+-- Check WHERE CURRENT OF with an index-only scan
+BEGIN;
+EXPLAIN (costs off)
+DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA';
+                 QUERY PLAN                  
+---------------------------------------------
+ Index Only Scan using onek_stringu1 on onek
+   Index Cond: (stringu1 = 'DZAAAA'::name)
+(2 rows)
+
+DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA';
+FETCH FROM c1;
+ stringu1 
+----------
+ DZAAAA
+(1 row)
+
+DELETE FROM onek WHERE CURRENT OF c1;
+SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA';
+ stringu1 
+----------
+(0 rows)
+
 ROLLBACK;
 -- Make sure snapshot management works okay, per bug report in
 -- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com
index a1c812e937f78a5d45974d97ee7f9233af0dc6c0..d1a589094eadefbce03203596aad7add81f80d42 100644 (file)
@@ -462,6 +462,16 @@ FETCH FROM c1;
 DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported
 ROLLBACK;
 
+-- Check WHERE CURRENT OF with an index-only scan
+BEGIN;
+EXPLAIN (costs off)
+DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA';
+DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA';
+FETCH FROM c1;
+DELETE FROM onek WHERE CURRENT OF c1;
+SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA';
+ROLLBACK;
+
 -- Make sure snapshot management works okay, per bug report in
 -- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com
 BEGIN;