static char *fetch_cursor_param_value(ExprContext *econtext, int paramId);
-static ScanState *search_plan_tree(PlanState *node, Oid table_oid);
+static ScanState *search_plan_tree(PlanState *node, Oid table_oid,
+ bool *pending_rescan);
/*
* aggregation.
*/
ScanState *scanstate;
+ bool pending_rescan = false;
- scanstate = search_plan_tree(queryDesc->planstate, table_oid);
+ scanstate = search_plan_tree(queryDesc->planstate, table_oid,
+ &pending_rescan);
if (!scanstate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" is not positioned on a row",
cursor_name)));
- /* Now OK to return false if we found an inactive scan */
- if (TupIsNull(scanstate->ss_ScanTupleSlot))
+ /*
+ * Now OK to return false if we found an inactive scan. It is
+ * inactive either if it's not positioned on a row, or there's a
+ * rescan pending for it.
+ */
+ if (TupIsNull(scanstate->ss_ScanTupleSlot) || pending_rescan)
return false;
/*
*
* Search through a PlanState tree for a scan node on the specified table.
* Return NULL if not found or multiple candidates.
+ *
+ * If a candidate is found, set *pending_rescan to true if that candidate
+ * or any node above it has a pending rescan action, i.e. chgParam != NULL.
+ * That indicates that we shouldn't consider the node to be positioned on a
+ * valid tuple, even if its own state would indicate that it is. (Caller
+ * must initialize *pending_rescan to false, and should not trust its state
+ * if multiple candidates are found.)
*/
static ScanState *
-search_plan_tree(PlanState *node, Oid table_oid)
+search_plan_tree(PlanState *node, Oid table_oid,
+ bool *pending_rescan)
{
+ ScanState *result = NULL;
+
if (node == NULL)
return NULL;
switch (nodeTag(node))
ScanState *sstate = (ScanState *) node;
if (RelationGetRelid(sstate->ss_currentRelation) == table_oid)
- return sstate;
+ result = sstate;
break;
}
case T_AppendState:
{
AppendState *astate = (AppendState *) node;
- ScanState *result = NULL;
int i;
for (i = 0; i < astate->as_nplans; i++)
{
ScanState *elem = search_plan_tree(astate->appendplans[i],
- table_oid);
+ table_oid,
+ pending_rescan);
if (!elem)
continue;
return NULL; /* multiple matches */
result = elem;
}
- return result;
+ break;
}
/*
case T_MergeAppendState:
{
MergeAppendState *mstate = (MergeAppendState *) node;
- ScanState *result = NULL;
int i;
for (i = 0; i < mstate->ms_nplans; i++)
{
ScanState *elem = search_plan_tree(mstate->mergeplans[i],
- table_oid);
+ table_oid,
+ pending_rescan);
if (!elem)
continue;
return NULL; /* multiple matches */
result = elem;
}
- return result;
+ break;
}
/*
*/
case T_ResultState:
case T_LimitState:
- return search_plan_tree(node->lefttree, table_oid);
+ result = search_plan_tree(node->lefttree,
+ table_oid,
+ pending_rescan);
+ break;
/*
* SubqueryScan too, but it keeps the child in a different place
*/
case T_SubqueryScanState:
- return search_plan_tree(((SubqueryScanState *) node)->subplan,
- table_oid);
+ result = search_plan_tree(((SubqueryScanState *) node)->subplan,
+ table_oid,
+ pending_rescan);
+ break;
default:
/* Otherwise, assume we can't descend through it */
break;
}
- return NULL;
+
+ /*
+ * If we found a candidate at or below this node, then this node's
+ * chgParam indicates a pending rescan that will affect the candidate.
+ */
+ if (result && node->chgParam != NULL)
+ *pending_rescan = true;
+
+ return result;
}
----------
(0 rows)
+ROLLBACK;
+-- Check behavior with rewinding to a previous child scan node,
+-- as per bug #15395
+BEGIN;
+CREATE TABLE current_check (currentid int, payload text);
+CREATE TABLE current_check_1 () INHERITS (current_check);
+CREATE TABLE current_check_2 () INHERITS (current_check);
+INSERT INTO current_check_1 SELECT i, 'p' || i FROM generate_series(1,9) i;
+INSERT INTO current_check_2 SELECT i, 'P' || i FROM generate_series(10,19) i;
+DECLARE c1 SCROLL CURSOR FOR SELECT * FROM current_check;
+-- This tests the fetch-backwards code path
+FETCH ABSOLUTE 12 FROM c1;
+ currentid | payload
+-----------+---------
+ 12 | P12
+(1 row)
+
+FETCH ABSOLUTE 8 FROM c1;
+ currentid | payload
+-----------+---------
+ 8 | p8
+(1 row)
+
+DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *;
+ currentid | payload
+-----------+---------
+ 8 | p8
+(1 row)
+
+-- This tests the ExecutorRewind code path
+FETCH ABSOLUTE 13 FROM c1;
+ currentid | payload
+-----------+---------
+ 13 | P13
+(1 row)
+
+FETCH ABSOLUTE 1 FROM c1;
+ currentid | payload
+-----------+---------
+ 1 | p1
+(1 row)
+
+DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *;
+ currentid | payload
+-----------+---------
+ 1 | p1
+(1 row)
+
+SELECT * FROM current_check;
+ currentid | payload
+-----------+---------
+ 2 | p2
+ 3 | p3
+ 4 | p4
+ 5 | p5
+ 6 | p6
+ 7 | p7
+ 9 | p9
+ 10 | P10
+ 11 | P11
+ 12 | P12
+ 13 | P13
+ 14 | P14
+ 15 | P15
+ 16 | P16
+ 17 | P17
+ 18 | P18
+ 19 | P19
+(17 rows)
+
ROLLBACK;
-- Make sure snapshot management works okay, per bug report in
-- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com
SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA';
ROLLBACK;
+-- Check behavior with rewinding to a previous child scan node,
+-- as per bug #15395
+BEGIN;
+CREATE TABLE current_check (currentid int, payload text);
+CREATE TABLE current_check_1 () INHERITS (current_check);
+CREATE TABLE current_check_2 () INHERITS (current_check);
+INSERT INTO current_check_1 SELECT i, 'p' || i FROM generate_series(1,9) i;
+INSERT INTO current_check_2 SELECT i, 'P' || i FROM generate_series(10,19) i;
+
+DECLARE c1 SCROLL CURSOR FOR SELECT * FROM current_check;
+
+-- This tests the fetch-backwards code path
+FETCH ABSOLUTE 12 FROM c1;
+FETCH ABSOLUTE 8 FROM c1;
+DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *;
+
+-- This tests the ExecutorRewind code path
+FETCH ABSOLUTE 13 FROM c1;
+FETCH ABSOLUTE 1 FROM c1;
+DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *;
+
+SELECT * FROM current_check;
+ROLLBACK;
+
-- Make sure snapshot management works okay, per bug report in
-- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com
BEGIN;