diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index a2f67f2332..c45a48812b 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -1366,6 +1366,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 */ diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c index ce7d4ac592..f70e54fe2a 100644 --- a/src/backend/executor/execCurrent.c +++ b/src/backend/executor/execCurrent.c @@ -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); + /* + * 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; - Assert(tuple_tableoid == table_oid); + *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; - *current_tid = *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; } diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h index 0642a3ada5..a5779b15ea 100644 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -170,5 +170,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 */ diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out index 1b8f7b69d1..048b2fc3e3 100644 --- a/src/test/regress/expected/portals.out +++ b/src/test/regress/expected/portals.out @@ -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 diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql index a1c812e937..d1a589094e 100644 --- a/src/test/regress/sql/portals.sql +++ b/src/test/regress/sql/portals.sql @@ -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;