aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/portalcmds.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2021-09-10 13:18:32 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2021-09-10 13:18:32 -0400
commit9ea8d3d24a9fb2bbe99cb17f6ee08747f662aabd (patch)
tree6dffd0fbdbbe8abdc019789e27afc637df784614 /src/backend/commands/portalcmds.c
parent7813451c20c3b341c8af542d80bb941c9c893825 (diff)
downloadpostgresql-9ea8d3d24a9fb2bbe99cb17f6ee08747f662aabd.tar.gz
postgresql-9ea8d3d24a9fb2bbe99cb17f6ee08747f662aabd.zip
Fix some anomalies with NO SCROLL cursors.
We have long forbidden fetching backwards from a NO SCROLL cursor, but the prohibition didn't extend to cases in which we rewind the query altogether and then re-fetch forwards. I think the reason is that this logic was mainly meant to protect plan nodes that can't be run in the reverse direction. However, re-reading the query output is problematic if the query is volatile (which includes SELECT FOR UPDATE, not just queries with volatile functions): the re-read can produce different results, which confuses the cursor navigation logic completely. Another reason for disliking this approach is that some code paths will either fetch backwards or rewind-and-fetch-forwards depending on the distance to the target row; so that seemingly identical use-cases may or may not draw the "cursor can only scan forward" error. Hence, let's clean things up by disallowing rewind as well as fetch-backwards in a NO SCROLL cursor. Ordinarily we'd only make such a definitional change in HEAD, but there is a third reason to consider this change now. Commit ba2c6d6ce created some new user-visible anomalies for non-scrollable cursors WITH HOLD, in that navigation in the cursor result got confused if the cursor had been partially read before committing. The only good way to resolve those anomalies is to forbid rewinding such a cursor, which allows removal of the incorrect cursor state manipulations that ba2c6d6ce added to PersistHoldablePortal. To minimize the behavioral change in the back branches (including v14), refuse to rewind a NO SCROLL cursor only when it has a holdStore, ie has been held over from a previous transaction due to WITH HOLD. This should avoid breaking most applications that have been sloppy about whether to declare cursors as scrollable. We'll enforce the prohibition across-the-board beginning in v15. Back-patch to v11, as ba2c6d6ce was. Discussion: https://postgr.es/m/3712911.1631207435@sss.pgh.pa.us
Diffstat (limited to 'src/backend/commands/portalcmds.c')
-rw-r--r--src/backend/commands/portalcmds.c28
1 files changed, 20 insertions, 8 deletions
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 1bd2bf39b15..a413e8beed9 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -382,6 +382,15 @@ PersistHoldablePortal(Portal portal)
* can be processed. Otherwise, store only the not-yet-fetched rows.
* (The latter is not only more efficient, but avoids semantic
* problems if the query's output isn't stable.)
+ *
+ * In the no-scroll case, tuple indexes in the tuplestore will not
+ * match the cursor's nominal position (portalPos). Currently this
+ * causes no difficulty because we only navigate in the tuplestore by
+ * relative position, except for the tuplestore_skiptuples call below
+ * and the tuplestore_rescan call in DoPortalRewind, both of which are
+ * disabled for no-scroll cursors. But someday we might need to track
+ * the offset between the holdStore and the cursor's nominal position
+ * explicitly.
*/
if (portal->cursorOptions & CURSOR_OPT_SCROLL)
{
@@ -389,10 +398,6 @@ PersistHoldablePortal(Portal portal)
}
else
{
- /* Disallow moving backwards from here */
- portal->atStart = true;
- portal->portalPos = 0;
-
/*
* If we already reached end-of-query, set the direction to
* NoMovement to avoid trying to fetch any tuples. (This check
@@ -448,10 +453,17 @@ PersistHoldablePortal(Portal portal)
{
tuplestore_rescan(portal->holdStore);
- if (!tuplestore_skiptuples(portal->holdStore,
- portal->portalPos,
- true))
- elog(ERROR, "unexpected end of tuple stream");
+ /*
+ * In the no-scroll case, the start of the tuplestore is exactly
+ * where we want to be, so no repositioning is wanted.
+ */
+ if (portal->cursorOptions & CURSOR_OPT_SCROLL)
+ {
+ if (!tuplestore_skiptuples(portal->holdStore,
+ portal->portalPos,
+ true))
+ elog(ERROR, "unexpected end of tuple stream");
+ }
}
}
PG_CATCH();