1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-03 20:02:46 +03:00

Fix WHERE CURRENT OF when the referenced cursor uses an index-only scan.

"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
This commit is contained in:
Tom Lane
2018-03-17 14:59:31 -04:00
parent e400840b1d
commit 8f5ac44043
5 changed files with 119 additions and 18 deletions

View 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);
/*
* 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;
}