aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2021-06-08 18:40:06 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2021-06-08 18:40:06 -0400
commitbe90098907475f3cfff7dc6d590641b9c2dae081 (patch)
tree37fe5e30e1c4d36dc0695b45f8955962064d4e98
parentba2c6d6cec000f0aeaeda4d56a23a335f6164860 (diff)
downloadpostgresql-be90098907475f3cfff7dc6d590641b9c2dae081.tar.gz
postgresql-be90098907475f3cfff7dc6d590641b9c2dae081.zip
Force NO SCROLL for plpgsql's implicit cursors.
Further thought about bug #17050 suggests that it's a good idea to use CURSOR_OPT_NO_SCROLL for the implicit cursor opened by a plpgsql FOR-over-query loop. This ensures that, if somebody commits inside the loop, PersistHoldablePortal won't try to rewind and re-read the cursor. While we'd have selected NO_SCROLL anyway if FOR UPDATE/SHARE appears in the query, there are other hazards with volatile functions; and in any case, it's silly to expend effort storing rows that we know for certain won't be needed. (While here, improve the comment in exec_run_select, which was a bit confused about the rationale for when we can use parallel mode. Cursor operations aren't a hazard for nameless portals.) This wasn't an issue until v11, which introduced the possibility of persisting such cursors. Hence, back-patch to v11. Per bug #17050 from Алексей Булгаков. Discussion: https://postgr.es/m/17050-f77aa827dc85247c@postgresql.org
-rw-r--r--src/pl/plpgsql/src/pl_exec.c21
1 files changed, 14 insertions, 7 deletions
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 78b593d12c7..207c4424fac 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4561,7 +4561,7 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
int rc;
portal = exec_dynquery_with_params(estate, stmt->query, stmt->params,
- NULL, 0);
+ NULL, CURSOR_OPT_NO_SCROLL);
/*
* Execute the loop
@@ -5694,14 +5694,21 @@ exec_run_select(PLpgSQL_execstate *estate,
* On the first call for this expression generate the plan.
*
* If we don't need to return a portal, then we're just going to execute
- * the query once, which means it's OK to use a parallel plan, even if the
- * number of rows being fetched is limited. If we do need to return a
- * portal, the caller might do cursor operations, which parallel query
- * can't support.
+ * the query immediately, which means it's OK to use a parallel plan, even
+ * if the number of rows being fetched is limited. If we do need to
+ * return a portal (i.e., this is for a FOR loop), the user's code might
+ * invoke additional operations inside the FOR loop, making parallel query
+ * unsafe. In any case, we don't expect any cursor operations to be done,
+ * so specify NO_SCROLL for efficiency and semantic safety.
*/
if (expr->plan == NULL)
- exec_prepare_plan(estate, expr,
- portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0);
+ {
+ int cursorOptions = CURSOR_OPT_NO_SCROLL;
+
+ if (portalP == NULL)
+ cursorOptions |= CURSOR_OPT_PARALLEL_OK;
+ exec_prepare_plan(estate, expr, cursorOptions);
+ }
/*
* Set up ParamListInfo to pass to executor