mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03:00
Fix interaction between materializing holdable cursors and firing
deferred triggers: either one can create more work for the other, so we have to loop till it's all gone. Per example from andrew@supernews. Add a regression test to help spot trouble in this area in future.
This commit is contained in:
parent
0c400f1bbc
commit
c3294f1cbf
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.198 2005/03/28 01:50:33 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.199 2005/04/11 19:51:14 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -1439,16 +1439,32 @@ CommitTransaction(void)
|
|||||||
/*
|
/*
|
||||||
* Do pre-commit processing (most of this stuff requires database
|
* Do pre-commit processing (most of this stuff requires database
|
||||||
* access, and in fact could still cause an error...)
|
* access, and in fact could still cause an error...)
|
||||||
|
*
|
||||||
|
* It is possible for CommitHoldablePortals to invoke functions that
|
||||||
|
* queue deferred triggers, and it's also possible that triggers create
|
||||||
|
* holdable cursors. So we have to loop until there's nothing left to
|
||||||
|
* do.
|
||||||
*/
|
*/
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Fire all currently pending deferred triggers.
|
||||||
|
*/
|
||||||
|
AfterTriggerFireDeferred();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tell the trigger manager that this transaction is about to be
|
* Convert any open holdable cursors into static portals. If there
|
||||||
* committed. He'll invoke all trigger deferred until XACT before we
|
* weren't any, we are done ... otherwise loop back to check if they
|
||||||
* really start on committing the transaction.
|
* queued deferred triggers. Lather, rinse, repeat.
|
||||||
*/
|
*/
|
||||||
AfterTriggerEndXact();
|
if (!CommitHoldablePortals())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Close open cursors */
|
/* Now we can shut down the deferred-trigger manager */
|
||||||
|
AfterTriggerEndXact(true);
|
||||||
|
|
||||||
|
/* Close any open regular cursors */
|
||||||
AtCommit_Portals();
|
AtCommit_Portals();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1649,7 +1665,7 @@ AbortTransaction(void)
|
|||||||
/*
|
/*
|
||||||
* do abort processing
|
* do abort processing
|
||||||
*/
|
*/
|
||||||
AfterTriggerAbortXact();
|
AfterTriggerEndXact(false);
|
||||||
AtAbort_Portals();
|
AtAbort_Portals();
|
||||||
AtEOXact_LargeObject(false); /* 'false' means it's abort */
|
AtEOXact_LargeObject(false); /* 'false' means it's abort */
|
||||||
AtAbort_Notify();
|
AtAbort_Notify();
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.183 2005/03/29 03:01:30 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.184 2005/04/11 19:51:15 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -2441,14 +2441,18 @@ AfterTriggerEndQuery(EState *estate)
|
|||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* AfterTriggerEndXact()
|
* AfterTriggerFireDeferred()
|
||||||
*
|
*
|
||||||
* Called just before the current transaction is committed. At this
|
* Called just before the current transaction is committed. At this
|
||||||
* time we invoke all DEFERRED triggers and tidy up.
|
* time we invoke all pending DEFERRED triggers.
|
||||||
|
*
|
||||||
|
* It is possible for other modules to queue additional deferred triggers
|
||||||
|
* during pre-commit processing; therefore xact.c may have to call this
|
||||||
|
* multiple times.
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
AfterTriggerEndXact(void)
|
AfterTriggerFireDeferred(void)
|
||||||
{
|
{
|
||||||
AfterTriggerEventList *events;
|
AfterTriggerEventList *events;
|
||||||
|
|
||||||
@ -2463,14 +2467,14 @@ AfterTriggerEndXact(void)
|
|||||||
* for them to use. (Since PortalRunUtility doesn't set a snap for
|
* for them to use. (Since PortalRunUtility doesn't set a snap for
|
||||||
* COMMIT, we can't assume ActiveSnapshot is valid on entry.)
|
* COMMIT, we can't assume ActiveSnapshot is valid on entry.)
|
||||||
*/
|
*/
|
||||||
if (afterTriggers->events.head != NULL)
|
events = &afterTriggers->events;
|
||||||
|
if (events->head != NULL)
|
||||||
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
|
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Run all the remaining triggers. Loop until they are all gone,
|
* Run all the remaining triggers. Loop until they are all gone,
|
||||||
* just in case some trigger queues more for us to do.
|
* just in case some trigger queues more for us to do.
|
||||||
*/
|
*/
|
||||||
events = &afterTriggers->events;
|
|
||||||
while (afterTriggerMarkEvents(events, NULL, false))
|
while (afterTriggerMarkEvents(events, NULL, false))
|
||||||
{
|
{
|
||||||
CommandId firing_id = afterTriggers->firing_counter++;
|
CommandId firing_id = afterTriggers->firing_counter++;
|
||||||
@ -2478,34 +2482,26 @@ AfterTriggerEndXact(void)
|
|||||||
afterTriggerInvokeEvents(events, firing_id, NULL, true);
|
afterTriggerInvokeEvents(events, firing_id, NULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
Assert(events->head == NULL);
|
||||||
* Forget everything we know about AFTER triggers.
|
|
||||||
*
|
|
||||||
* Since all the info is in TopTransactionContext or children thereof, we
|
|
||||||
* need do nothing special to reclaim memory.
|
|
||||||
*/
|
|
||||||
afterTriggers = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* AfterTriggerAbortXact()
|
* AfterTriggerEndXact()
|
||||||
*
|
*
|
||||||
* The current transaction has entered the abort state.
|
* The current transaction is finishing.
|
||||||
* All outstanding triggers are canceled so we simply throw
|
*
|
||||||
|
* Any unfired triggers are canceled so we simply throw
|
||||||
* away anything we know.
|
* away anything we know.
|
||||||
|
*
|
||||||
|
* Note: it is possible for this to be called repeatedly in case of
|
||||||
|
* error during transaction abort; therefore, do not complain if
|
||||||
|
* already closed down.
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
AfterTriggerAbortXact(void)
|
AfterTriggerEndXact(bool isCommit)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* Ignore call if we aren't in a transaction. (Need this to survive
|
|
||||||
* repeat call in case of error during transaction abort.)
|
|
||||||
*/
|
|
||||||
if (afterTriggers == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Forget everything we know about AFTER triggers.
|
* Forget everything we know about AFTER triggers.
|
||||||
*
|
*
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.77 2005/01/26 23:20:21 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.78 2005/04/11 19:51:15 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -416,7 +416,64 @@ DropDependentPortals(MemoryContext queryContext)
|
|||||||
*
|
*
|
||||||
* Any holdable cursors created in this transaction need to be converted to
|
* Any holdable cursors created in this transaction need to be converted to
|
||||||
* materialized form, since we are going to close down the executor and
|
* materialized form, since we are going to close down the executor and
|
||||||
* release locks. Remove all other portals created in this transaction.
|
* release locks. Other portals are not touched yet.
|
||||||
|
*
|
||||||
|
* Returns TRUE if any holdable cursors were processed, FALSE if not.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
CommitHoldablePortals(void)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
HASH_SEQ_STATUS status;
|
||||||
|
PortalHashEnt *hentry;
|
||||||
|
|
||||||
|
hash_seq_init(&status, PortalHashTable);
|
||||||
|
|
||||||
|
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
|
||||||
|
{
|
||||||
|
Portal portal = hentry->portal;
|
||||||
|
|
||||||
|
/* Is it a holdable portal created in the current xact? */
|
||||||
|
if ((portal->cursorOptions & CURSOR_OPT_HOLD) &&
|
||||||
|
portal->createSubid != InvalidSubTransactionId &&
|
||||||
|
portal->status == PORTAL_READY)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We are exiting the transaction that created a holdable
|
||||||
|
* cursor. Instead of dropping the portal, prepare it for
|
||||||
|
* access by later transactions.
|
||||||
|
*
|
||||||
|
* Note that PersistHoldablePortal() must release all resources
|
||||||
|
* used by the portal that are local to the creating
|
||||||
|
* transaction.
|
||||||
|
*/
|
||||||
|
PortalCreateHoldStore(portal);
|
||||||
|
PersistHoldablePortal(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 successfully exported the holdable cursor, mark it
|
||||||
|
* as not belonging to this transaction.
|
||||||
|
*/
|
||||||
|
portal->createSubid = InvalidSubTransactionId;
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pre-commit processing for portals.
|
||||||
|
*
|
||||||
|
* Remove all non-holdable portals created in this transaction.
|
||||||
* Portals remaining from prior transactions should be left untouched.
|
* Portals remaining from prior transactions should be left untouched.
|
||||||
*
|
*
|
||||||
* XXX This assumes that portals can be deleted in a random order, ie,
|
* XXX This assumes that portals can be deleted in a random order, ie,
|
||||||
@ -451,47 +508,16 @@ AtCommit_Portals(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Do nothing else to cursors held over from a previous
|
* Do nothing to cursors held over from a previous transaction
|
||||||
* transaction.
|
* (including holdable ones just frozen by CommitHoldablePortals).
|
||||||
*/
|
*/
|
||||||
if (portal->createSubid == InvalidSubTransactionId)
|
if (portal->createSubid == InvalidSubTransactionId)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if ((portal->cursorOptions & CURSOR_OPT_HOLD) &&
|
|
||||||
portal->status == PORTAL_READY)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* We are exiting the transaction that created a holdable
|
|
||||||
* cursor. Instead of dropping the portal, prepare it for
|
|
||||||
* access by later transactions.
|
|
||||||
*
|
|
||||||
* Note that PersistHoldablePortal() must release all resources
|
|
||||||
* used by the portal that are local to the creating
|
|
||||||
* transaction.
|
|
||||||
*/
|
|
||||||
PortalCreateHoldStore(portal);
|
|
||||||
PersistHoldablePortal(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 successfully exported the holdable cursor, mark it
|
|
||||||
* as not belonging to this transaction.
|
|
||||||
*/
|
|
||||||
portal->createSubid = InvalidSubTransactionId;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Zap all non-holdable portals */
|
/* Zap all non-holdable portals */
|
||||||
PortalDrop(portal, true);
|
PortalDrop(portal, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Abort processing for portals.
|
* Abort processing for portals.
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.52 2005/03/25 21:57:59 tgl Exp $
|
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.53 2005/04/11 19:51:15 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -157,8 +157,8 @@ extern void ExecARUpdateTriggers(EState *estate,
|
|||||||
extern void AfterTriggerBeginXact(void);
|
extern void AfterTriggerBeginXact(void);
|
||||||
extern void AfterTriggerBeginQuery(void);
|
extern void AfterTriggerBeginQuery(void);
|
||||||
extern void AfterTriggerEndQuery(EState *estate);
|
extern void AfterTriggerEndQuery(EState *estate);
|
||||||
extern void AfterTriggerEndXact(void);
|
extern void AfterTriggerFireDeferred(void);
|
||||||
extern void AfterTriggerAbortXact(void);
|
extern void AfterTriggerEndXact(bool isCommit);
|
||||||
extern void AfterTriggerBeginSubXact(void);
|
extern void AfterTriggerBeginSubXact(void);
|
||||||
extern void AfterTriggerEndSubXact(bool isCommit);
|
extern void AfterTriggerEndSubXact(bool isCommit);
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.54 2004/12/31 22:03:46 pgsql Exp $
|
* $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.55 2005/04/11 19:51:16 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -182,6 +182,7 @@ typedef struct PortalData
|
|||||||
|
|
||||||
/* Prototypes for functions in utils/mmgr/portalmem.c */
|
/* Prototypes for functions in utils/mmgr/portalmem.c */
|
||||||
extern void EnablePortalManager(void);
|
extern void EnablePortalManager(void);
|
||||||
|
extern bool CommitHoldablePortals(void);
|
||||||
extern void AtCommit_Portals(void);
|
extern void AtCommit_Portals(void);
|
||||||
extern void AtAbort_Portals(void);
|
extern void AtAbort_Portals(void);
|
||||||
extern void AtCleanup_Portals(void);
|
extern void AtCleanup_Portals(void);
|
||||||
|
@ -773,3 +773,38 @@ FETCH ALL FROM c;
|
|||||||
(15 rows)
|
(15 rows)
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
--
|
||||||
|
-- Test behavior of both volatile and stable functions inside a cursor;
|
||||||
|
-- in particular we want to see what happens during commit of a holdable
|
||||||
|
-- cursor
|
||||||
|
--
|
||||||
|
create temp table tt1(f1 int);
|
||||||
|
create function count_tt1_v() returns int8 as
|
||||||
|
'select count(*) from tt1' language sql volatile;
|
||||||
|
create function count_tt1_s() returns int8 as
|
||||||
|
'select count(*) from tt1' language sql stable;
|
||||||
|
begin;
|
||||||
|
insert into tt1 values(1);
|
||||||
|
declare c1 cursor for select count_tt1_v(), count_tt1_s();
|
||||||
|
insert into tt1 values(2);
|
||||||
|
fetch all from c1;
|
||||||
|
count_tt1_v | count_tt1_s
|
||||||
|
-------------+-------------
|
||||||
|
2 | 1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
rollback;
|
||||||
|
begin;
|
||||||
|
insert into tt1 values(1);
|
||||||
|
declare c2 cursor with hold for select count_tt1_v(), count_tt1_s();
|
||||||
|
insert into tt1 values(2);
|
||||||
|
commit;
|
||||||
|
delete from tt1;
|
||||||
|
fetch all from c2;
|
||||||
|
count_tt1_v | count_tt1_s
|
||||||
|
-------------+-------------
|
||||||
|
2 | 1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
drop function count_tt1_v();
|
||||||
|
drop function count_tt1_s();
|
||||||
|
@ -235,3 +235,46 @@ SELECT declares_cursor('AB%');
|
|||||||
FETCH ALL FROM c;
|
FETCH ALL FROM c;
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test behavior of both volatile and stable functions inside a cursor;
|
||||||
|
-- in particular we want to see what happens during commit of a holdable
|
||||||
|
-- cursor
|
||||||
|
--
|
||||||
|
|
||||||
|
create temp table tt1(f1 int);
|
||||||
|
|
||||||
|
create function count_tt1_v() returns int8 as
|
||||||
|
'select count(*) from tt1' language sql volatile;
|
||||||
|
|
||||||
|
create function count_tt1_s() returns int8 as
|
||||||
|
'select count(*) from tt1' language sql stable;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
insert into tt1 values(1);
|
||||||
|
|
||||||
|
declare c1 cursor for select count_tt1_v(), count_tt1_s();
|
||||||
|
|
||||||
|
insert into tt1 values(2);
|
||||||
|
|
||||||
|
fetch all from c1;
|
||||||
|
|
||||||
|
rollback;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
insert into tt1 values(1);
|
||||||
|
|
||||||
|
declare c2 cursor with hold for select count_tt1_v(), count_tt1_s();
|
||||||
|
|
||||||
|
insert into tt1 values(2);
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
delete from tt1;
|
||||||
|
|
||||||
|
fetch all from c2;
|
||||||
|
|
||||||
|
drop function count_tt1_v();
|
||||||
|
drop function count_tt1_s();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user