Re: committing inside cursor loop
> "Peter" == Peter Eisentrautwrites: >> So... what is a pl/* that does _not_ use pinned cursors for cursor >> loops supposed to do in this case? Peter> Other than maybe using pinned cursors, one would have to look at Peter> the whole picture and see what makes sense. Never mind, I was overlooking the obvious: explicitly specifying CURSOR_OPT_HOLD is sufficient for me. (Can't really use pinned cursors because they might not be unpinned fast enough due to timing of garbage collection, which would lead to spurious errors.) -- Andrew (irc:RhodiumToad)
Re: committing inside cursor loop
On 3/29/18 07:37, Andrew Gierth wrote: >> "Peter" == Peter Eisentrautwrites: > > Peter> Committed, thanks! > > So... what is a pl/* that does _not_ use pinned cursors for cursor loops > supposed to do in this case? Other than maybe using pinned cursors, one would have to look at the whole picture and see what makes sense. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: committing inside cursor loop
> "Peter" == Peter Eisentrautwrites: Peter> Committed, thanks! So... what is a pl/* that does _not_ use pinned cursors for cursor loops supposed to do in this case? -- Andrew (irc:RhodiumToad)
Re: committing inside cursor loop
On 3/28/18 11:34, Ildus Kurbangaliev wrote: > The following review has been posted through the commitfest application: > make installcheck-world: tested, passed > Implements feature: tested, passed > Spec compliant: tested, passed > Documentation:tested, passed > > I have checked new version. Although I can miss something, the patch looks > good to me. > > The new status of this patch is: Ready for Committer Committed, thanks! -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: committing inside cursor loop
The following review has been posted through the commitfest application: make installcheck-world: tested, passed Implements feature: tested, passed Spec compliant: tested, passed Documentation:tested, passed I have checked new version. Although I can miss something, the patch looks good to me. The new status of this patch is: Ready for Committer
Re: committing inside cursor loop
On 3/19/18 20:40, Peter Eisentraut wrote: > On 3/14/18 08:05, Ildus Kurbangaliev wrote: >>> The ROLLBACK call in the first loop iteration undoes the UPDATE >>> command that drives the loop. Is it then sensible to continue the >>> loop? >>> >> I think that in the first place ROLLBACK was prohibited because of cases >> like this, but it seems to safe to continue the loop when portal >> strategy is PORTAL_ONE_SELECT. > > Maybe, but even plain SELECT commands can have side effects. Here is an updated patch that supports the ROLLBACK case as well, and prevents holding portals with a strategy other than PORTAL_ONE_SELECT. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services From 1627af796da44d03f2d0d739250621be1c3244a3 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <pete...@gmx.net> Date: Mon, 26 Mar 2018 15:40:49 -0400 Subject: [PATCH v2] PL/pgSQL: Allow committing inside cursor loop Previously, committing or aborting inside a cursor loop was prohibited because that would close and remove the cursor. To allow that, automatically convert such cursors to holdable cursors so they survive commits or rollbacks. Portals now have a new state "auto-held", which means they have been converted automatically from pinned. An auto-held portal is kept on transaction commit or rollback, but is still removed when returning to the main loop on error. --- doc/src/sgml/plpgsql.sgml | 37 +- src/backend/tcop/postgres.c| 2 + src/backend/utils/mmgr/portalmem.c | 145 + src/include/utils/portal.h | 4 + .../plpgsql/src/expected/plpgsql_transaction.out | 108 ++- src/pl/plpgsql/src/pl_exec.c | 18 +-- src/pl/plpgsql/src/sql/plpgsql_transaction.sql | 67 ++ 7 files changed, 333 insertions(+), 48 deletions(-) diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 7ed926fd51..2043e644a9 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -3496,8 +3496,41 @@ Transaction Management -A transaction cannot be ended inside a loop over a query result, nor -inside a block with exception handlers. +Special considerations apply to cursor loops. Consider this example: + +CREATE PROCEDURE transaction_test2() +LANGUAGE plpgsql +AS $$ +DECLARE +r RECORD; +BEGIN +FOR r IN SELECT * FROM test2 ORDER BY x LOOP +INSERT INTO test1 (a) VALUES (r.x); +COMMIT; +END LOOP; +END; +$$; + +CALL transaction_test2(); + +Normally, cursors are automatically closed at transaction commit. +However, a cursor created as part of a loop like this is automatically +converted to a holdable cursor by the first COMMIT or +ROLLBACK. That means that the cursor is fully +evaluated at the first COMMIT or +ROLLBACK rather than row by row. The cursor is still +removed automatically after the loop, so this is mostly invisible to the +user. + + + +Transaction commands are not allowed in cursor loops driven by commands +that are not read-only (for example UPDATE +... RETURNING). + + + +A transaction cannot be ended inside a block with exception handlers. diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index f7aa4a7484..6fc1cc272b 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3938,6 +3938,8 @@ PostgresMain(int argc, char *argv[], if (am_walsender) WalSndErrorCleanup(); + PortalErrorCleanup(); + /* * We can't release replication slots inside AbortTransaction() as we * need to be able to start and abort transactions while having a slot diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 75a6dde32b..74a1040d8a 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -620,6 +620,36 @@ PortalHashTableDeleteAll(void) } } +/* + * "Hold" a portal. Prepare it for access by later transactions. + */ +static void +HoldPortal(Portal portal) +{ + /* +* Note that PersistHoldablePortal() must release all resources +* used by the portal that are local to the creating transaction. +*/ + PortalCreateHoldStore(portal); + PersistHoldablePortal(portal); + + /* drop cached plan reference, if any */ + PortalReleaseCachedPlan(portal); + + /* +* Any resources belonging to the portal will be released in the +* upcoming transaction-wide cleanup; the portal will no longer +* have its own resources. +*/ + portal->resowner = NULL; + + /* +* Having su
Re: committing inside cursor loop
On 3/14/18 08:05, Ildus Kurbangaliev wrote: >> The ROLLBACK call in the first loop iteration undoes the UPDATE >> command that drives the loop. Is it then sensible to continue the >> loop? >> > I think that in the first place ROLLBACK was prohibited because of cases > like this, but it seems to safe to continue the loop when portal > strategy is PORTAL_ONE_SELECT. Maybe, but even plain SELECT commands can have side effects. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: committing inside cursor loop
On Tue, 13 Mar 2018 11:08:36 -0400 Peter Eisentrautwrote: > On 3/6/18 07:48, Ildus Kurbangaliev wrote: > > Although as was discussed before it seems inconsistent without > > ROLLBACK support. There was a little discussion about it, but no > > replies. Maybe the status of the patch should be changed to > > 'Waiting on author' until the end of discussion. > > I'm wondering what the semantics of it should be. > > For example, consider this: > > drop table test1; > create table test1 (a int, b int); > insert into test1 values (1, 11), (2, 22), (3, 33); > > do > language plpgsql > $$ > declare > x int; > begin > for x in update test1 set b = b + 1 where b > 20 returning a loop > raise info 'x = %', x; > if x = 2 then >rollback; > end if; > end loop; > end; > $$; > > The ROLLBACK call in the first loop iteration undoes the UPDATE > command that drives the loop. Is it then sensible to continue the > loop? > I think that in the first place ROLLBACK was prohibited because of cases like this, but it seems to safe to continue the loop when portal strategy is PORTAL_ONE_SELECT. -- --- Ildus Kurbangaliev Postgres Professional: http://www.postgrespro.com Russian Postgres Company
Re: committing inside cursor loop
On 3/6/18 07:48, Ildus Kurbangaliev wrote: > Although as was discussed before it seems inconsistent without ROLLBACK > support. There was a little discussion about it, but no replies. Maybe > the status of the patch should be changed to 'Waiting on author' until > the end of discussion. I'm wondering what the semantics of it should be. For example, consider this: drop table test1; create table test1 (a int, b int); insert into test1 values (1, 11), (2, 22), (3, 33); do language plpgsql $$ declare x int; begin for x in update test1 set b = b + 1 where b > 20 returning a loop raise info 'x = %', x; if x = 2 then rollback; end if; end loop; end; $$; The ROLLBACK call in the first loop iteration undoes the UPDATE command that drives the loop. Is it then sensible to continue the loop? -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: committing inside cursor loop
On Tue, 20 Feb 2018 09:11:50 -0500 Peter Eisentrautwrote: > Here is a patch that allows COMMIT inside cursor loops in PL/pgSQL. > As alluded to in earlier threads, this is done by converting such > cursors to holdable automatically. A special flag "auto-held" marks > such cursors, so we know to clean them up on exceptions. > > This is currently only for PL/pgSQL, but the same technique could be > applied easily to other PLs. > Hi, The patch still applies, tests passed. The code looks nice, documentation and tests are there. Although as was discussed before it seems inconsistent without ROLLBACK support. There was a little discussion about it, but no replies. Maybe the status of the patch should be changed to 'Waiting on author' until the end of discussion. -- --- Ildus Kurbangaliev Postgres Professional: http://www.postgrespro.com Russian Postgres Company
Re: committing inside cursor loop
On 20 February 2018 at 14:45, Tom Lanewrote: > Peter Eisentraut writes: >> Here is a patch that allows COMMIT inside cursor loops in PL/pgSQL. As >> alluded to in earlier threads, this is done by converting such cursors >> to holdable automatically. A special flag "auto-held" marks such >> cursors, so we know to clean them up on exceptions. > > I haven't really read this patch, but this bit jumped out at me: > > +Inside a cursor loop, ROLLBACK is not allowed. (That > +would have to roll back the cursor query, thus invalidating the loop.) Hmm, why? Rollback would only invalidate the cursor if it hadn't yet hit a Commit. But if you did a commit, then the cursor would become holdable and you could happily continue reading through the loop even after the rollback. So if Commit changes a pinned portal into a holdable cursor, we just make Rollback do that also. Obviously only for pinned portals, i.e. the query/ies whose results we are currently looping through. -- Simon Riggshttp://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: committing inside cursor loop
On 20 February 2018 at 14:11, Peter Eisentrautwrote: > Here is a patch that allows COMMIT inside cursor loops in PL/pgSQL. As > alluded to in earlier threads, this is done by converting such cursors > to holdable automatically. A special flag "auto-held" marks such > cursors, so we know to clean them up on exceptions. > > This is currently only for PL/pgSQL, but the same technique could be > applied easily to other PLs. Amazingly clean, looks great. I notice that PersistHoldablePortal() does ExecutorRewind(). In most cases, the cursor loop doesn't ever rewind. So it would be good if we could pass a parameter that skips the rewind since it will never be needed and causes a performance hit. What I imagine is we can just persist the as-yet unfetched portion of the cursor from the current row onwards, rather than rewind and store the whole cursor result. -- Simon Riggshttp://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: committing inside cursor loop
Peter Eisentrautwrites: > Here is a patch that allows COMMIT inside cursor loops in PL/pgSQL. As > alluded to in earlier threads, this is done by converting such cursors > to holdable automatically. A special flag "auto-held" marks such > cursors, so we know to clean them up on exceptions. I haven't really read this patch, but this bit jumped out at me: +Inside a cursor loop, ROLLBACK is not allowed. (That +would have to roll back the cursor query, thus invalidating the loop.) Say what? That seems to translate into "we have lost the ability to deal with errors". I don't think this is really what people are hoping to get out of the transactional-procedure facility. regards, tom lane
committing inside cursor loop
Here is a patch that allows COMMIT inside cursor loops in PL/pgSQL. As alluded to in earlier threads, this is done by converting such cursors to holdable automatically. A special flag "auto-held" marks such cursors, so we know to clean them up on exceptions. This is currently only for PL/pgSQL, but the same technique could be applied easily to other PLs. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services From 649c84f49faa3f46e546ff9eb6d1b6e98483bdb8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <pete...@gmx.net> Date: Tue, 20 Feb 2018 08:52:23 -0500 Subject: [PATCH v1] PL/pgSQL: Allow committing inside cursor loop Previously, committing inside a cursor loop was prohibited because that would close and remove the cursor. To allow that, automatically convert such cursors to holdable cursors so they survive commits. Portals now have a new state "auto-held", which means they have been converted automatically from pinned. An auto-held portal is kept on transaction commit but is still removed on transaction abort. --- doc/src/sgml/plpgsql.sgml | 35 +- src/backend/utils/mmgr/portalmem.c | 49 -- src/include/utils/portal.h | 3 + .../plpgsql/src/expected/plpgsql_transaction.out | 74 +- src/pl/plpgsql/src/pl_exec.c | 9 +-- src/pl/plpgsql/src/sql/plpgsql_transaction.sql | 47 ++ 6 files changed, 199 insertions(+), 18 deletions(-) diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index c1e3c6a19d..6ac72cc5ac 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -3480,8 +3480,39 @@ Transaction Management -A transaction cannot be ended inside a loop over a query result, nor -inside a block with exception handlers. +Special considerations apply to cursor loops. Consider this example: + +CREATE PROCEDURE transaction_test2() +LANGUAGE plpgsql +AS $$ +DECLARE +r RECORD; +BEGIN +FOR r IN SELECT * FROM test2 ORDER BY x LOOP +INSERT INTO test1 (a) VALUES (r.x); +COMMIT; +END LOOP; +END; +$$; + +CALL transaction_test2(); + +Normally, cursors are automatically closed at transaction commit. +However, a cursor created as part of a loop like this is automatically +converted to a holdable cursor by the first COMMIT. +That means that the cursor is fully evaluated at the first +COMMIT rather than row by row. The cursor is still +removed automatically after the loop, so this is mostly invisible to the +user. + + + +Inside a cursor loop, ROLLBACK is not allowed. (That +would have to roll back the cursor query, thus invalidating the loop.) + + + +A transaction cannot be ended inside a block with exception handlers. diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 75a6dde32b..983a6d4436 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -648,9 +648,10 @@ PreCommit_Portals(bool isPrepare) /* * There should be no pinned portals anymore. Complain if someone -* leaked one. +* leaked one. Auto-held portals are allowed; we assume that whoever +* pinned them is managing them. */ - if (portal->portalPinned) + if (portal->portalPinned && !portal->autoHeld) elog(ERROR, "cannot commit while a portal is pinned"); /* @@ -766,9 +767,10 @@ AtAbort_Portals(void) MarkPortalFailed(portal); /* -* Do nothing else to cursors held over from a previous transaction. +* Do nothing else to cursors held over from a previous transaction, +* except auto-held ones. */ - if (portal->createSubid == InvalidSubTransactionId) + if (portal->createSubid == InvalidSubTransactionId && !portal->autoHeld) continue; /* @@ -834,8 +836,11 @@ AtCleanup_Portals(void) if (portal->status == PORTAL_ACTIVE) continue; - /* Do nothing to cursors held over from a previous transaction */ - if (portal->createSubid == InvalidSubTransactionId) + /* +* Do nothing to cursors held over from a previous transaction, except +* auto-held ones. +*/ + if (portal->createSubid == InvalidSubTransactionId && !portal->autoHeld) { Assert(portal->status != PORTAL_ACTIVE); Assert(por