diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/advanced.sgml 04pgproc/doc/src/sgml/advanced.sgml
*** 00orig/doc/src/sgml/advanced.sgml 2004-04-14 16:45:53.000000000 -0400
--- 04pgproc/doc/src/sgml/advanced.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 257,262 ****
--- 257,310 ----
you are using.
+
+
+ It's possible to control the statements in a transaction in a more
+ granular fashion through the use of savepoints>. Savepoints
+ allow you to selectively discard parts of the transaction, while
+ committing the rest. This is done be defining a savepoint with
+ SAVEPOINT>, to which you can later roll back using
+ ROLLBACK TO>. All statements between defining the savepoint
+ and rolling back to it will have no effect on the final transaction.
+
+
+
+ After rolling back to a savepoint, it continues to be defined, so you can
+ roll back to it several times. Conversely, if you are sure you won't need
+ to roll back to a particular savepoint again, it can be released, so the
+ system can free some resources. Keep in mind that releasing a savepoint
+ will automatically release all savepoints that were defined after it.
+
+
+
+ Remembering the bank database, suppose we debit $100.00 from Alice's
+ account, and credit Bob's account, only to find later that we wanted to
+ credit Wally's account. We could do it using savepoints like
+
+
+ BEGIN;
+ UPDATE accounts SET balance = balance - 100.00
+ WHERE name = 'Alice';
+ SAVEPOINT my_savepoint;
+ UPDATE accounts SET balance = balance + 100.00
+ WHERE name = 'Bob';
+ -- oops ... forget that and use Wally's account
+ ROLLBACK TO my_savepoint;
+ UPDATE accounts SET balance = balance + 100.00
+ WHERE name = 'Wally';
+ COMMIT;
+
+
+
+
+ This example is, of course, oversimplified, but there's a lot of control
+ to be had over a transaction block through the use of savepoints.
+ Moreover, ROLLBACK TO> is the only way to regain control of a
+ transaction block that was automatically put on aborted state by the
+ system for some reason, short of rolling it back completely and starting
+ again.
+
+
diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/ref/allfiles.sgml 04pgproc/doc/src/sgml/ref/allfiles.sgml
*** 00orig/doc/src/sgml/ref/allfiles.sgml 2004-06-26 00:28:45.000000000 -0400
--- 04pgproc/doc/src/sgml/ref/allfiles.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 88,96 ****
--- 88,99 ----
+
+
+
diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/ref/begin.sgml 04pgproc/doc/src/sgml/ref/begin.sgml
*** 00orig/doc/src/sgml/ref/begin.sgml 2004-01-11 06:24:17.000000000 -0300
--- 04pgproc/doc/src/sgml/ref/begin.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 145,150 ****
--- 145,151 ----
+
diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/ref/release.sgml 04pgproc/doc/src/sgml/ref/release.sgml
*** 00orig/doc/src/sgml/ref/release.sgml 1969-12-31 21:00:00.000000000 -0300
--- 04pgproc/doc/src/sgml/ref/release.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 0 ****
--- 1,138 ----
+
+
+
+
+ RELEASE
+ SQL - Language Statements
+
+
+
+ RELEASE
+ destroy a previously defined savepoint
+
+
+
+ RELEASE
+
+
+
+ savepoints
+ releasing
+
+
+
+
+ RELEASE savepoint_name
+
+
+
+
+ Description
+
+
+ RELEASE destroys a previously defined savepoint
+ in the current transaction.
+
+
+
+ Destroying a savepoint makes it—and all savepoints established after
+ it was established—unavailable as rollback points,
+ but it has no other user visible behavior. It does not undo the
+ effects of command executed after the savepoint was established.
+ To do that, see .
+
+
+
+ RELEASE also destroys all savepoints that were established
+ after the named savepoint was established.
+
+
+
+ Parameters
+
+
+
+ savepoint_name
+
+
+ The name of the savepoint to destroy.
+
+
+
+
+
+
+
+ Notes
+
+
+ Specifying a savepoint name that was not previously defined raises
+ an exception.
+
+
+
+ It is not possible to release a savepoint when the transaction is in
+ aborted state.
+
+
+
+ If multiple savepoints have the same name, only the one that was last
+ defined is released.
+
+
+
+
+
+ Examples
+
+
+ To establish and later destroy a savepoint:
+
+ BEGIN;
+ INSERT INTO table VALUES (3);
+ SAVEPOINT my_savepoint;
+ INSERT INTO table VALUES (4);
+ RELEASE my_savepoint;
+ COMMIT;
+
+
+
+ Compatibility
+
+
+ RELEASE is fully conforming to the SQL standard.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+
+
diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/ref/rollback.sgml 04pgproc/doc/src/sgml/ref/rollback.sgml
*** 00orig/doc/src/sgml/ref/rollback.sgml 2003-11-29 16:51:39.000000000 -0300
--- 04pgproc/doc/src/sgml/ref/rollback.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 90,95 ****
--- 90,96 ----
+
diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/ref/rollback_to.sgml 04pgproc/doc/src/sgml/ref/rollback_to.sgml
*** 00orig/doc/src/sgml/ref/rollback_to.sgml 1969-12-31 21:00:00.000000000 -0300
--- 04pgproc/doc/src/sgml/ref/rollback_to.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 0 ****
--- 1,158 ----
+
+
+
+
+ ROLLBACK TO
+ SQL - Language Statements
+
+
+
+ ROLLBACK TO
+ roll back to a savepoint
+
+
+
+ ROLLBACK TO
+
+
+
+ savepoints
+ rolling back
+
+
+
+
+ ROLLBACK TO savepoint_name
+
+
+
+
+ Description
+
+
+ Roll back all commands that were executed and destroy all savepoints that
+ were created after the savepoint was established. The savepoint is
+ automatically established again.
+
+
+
+ Parameters
+
+
+
+ savepoint_name>
+
+
+ The savepoint to roll back to.
+
+
+
+
+
+
+
+ Notes
+
+
+ Use to
+ destroy a savepoint without discarding the effects of commands executed
+ after it was established.
+
+
+
+ Specifying a savepoint name that has not been established causes an
+ exception to be raised.
+
+
+
+ Cursors have somewhat non-transactional behavior with respect to
+ savepoints. Any cursor that is opened inside the savepoint is not closed
+ when the savepoint is rolled back. If a cursor is affected by a
+ FETCH> command inside a savepoint that is later rolled
+ back, the cursor position remains at the position that FETCH>
+ left it pointing to (that is, FETCH> is not rolled back).
+ A cursor whose execution causes a transaction to abort is put in a
+ can't-execute state, so while the transaction can be restored using
+ ROLLBACK TO>, the cursor no longer can be used.
+
+
+
+
+ Examples
+
+
+ To undo the effects of the commands executed after my_savepoint
+ was established, and establish my_savepoint> again:
+
+ ROLLBACK TO my_savepoint;
+
+
+
+
+ Cursor positions are not affected by savepoint rollback:
+
+ BEGIN;
+
+ DECLARE foo CURSOR FOR SELECT 1 UNION SELECT 2;
+
+ SAVEPOINT foo;
+
+ FETCH 1 FROM foo;
+ ?column?
+ ----------
+ 1
+
+ ROLLBACK TO foo;
+
+ FETCH 1 FROM foo;
+ ?column?
+ ----------
+ 2
+
+ COMMIT;
+
+
+
+
+
+
+
+ Compatibility
+
+
+ This command is fully SQL standard conforming.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+
+
+
diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/ref/savepoint.sgml 04pgproc/doc/src/sgml/ref/savepoint.sgml
*** 00orig/doc/src/sgml/ref/savepoint.sgml 1969-12-31 21:00:00.000000000 -0300
--- 04pgproc/doc/src/sgml/ref/savepoint.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 0 ****
--- 1,153 ----
+
+
+
+
+ SAVEPOINT
+ SQL - Language Statements
+
+
+
+ SAVEPOINT
+ define a new savepoint within the current transaction
+
+
+
+ SAVEPOINT
+
+
+
+ savepoints
+ defining
+
+
+
+
+ SAVEPOINT savepoint_name
+
+
+
+
+ Description
+
+
+ SAVEPOINT establishes a new savepoint within
+ the current transaction.
+
+
+
+
+
+ Parameters
+
+
+
+ savepoint_name
+
+
+ The name to give to the new savepoint.
+
+
+
+
+
+
+
+ Notes
+
+
+ A savepoint is a special mark inside a transaction that allows all commands
+ that are executed after it was established to be rolled back.
+ Alternatively, a savepoint can be destroyed so that it isn't a possible
+ rollback destination anymore. In this case, all commands that were executed after
+ the savepoint was established are preserved.
+
+
+
+ Use to
+ rollback to a savepoint. Use to destroy a savepoint, keeping
+ the effects of commands executed after it was established.
+
+
+
+ Savepoints can only be established when inside a transaction block.
+ Issuing SAVEPOINT> when not inside a transaction block
+ will cause an exception to be raised.
+
+
+
+ There can be multiple savepoints defined within a transaction.
+
+
+
+
+ Examples
+
+
+ To establish a savepoint and undo the effects of all commands executed
+ after it was established, keeping only the first inserted value
+ in the table:
+
+ BEGIN;
+ INSERT INTO table VALUES (1);
+ SAVEPOINT my_savepoint;
+ INSERT INTO table VALUES (2);
+ ROLLBACK TO my_savepoint;
+ COMMIT;
+
+
+
+
+ To establish and later destroy a savepoint, keeping both values in the table:
+
+ BEGIN;
+ INSERT INTO table VALUES (3);
+ SAVEPOINT my_savepoint;
+ INSERT INTO table VALUES (4);
+ RELEASE my_savepoint;
+ COMMIT;
+
+
+
+ Compatibility
+
+
+ SQL requires a savepoint to be automatically destroyed when another savepoint
+ with the same name is established. In PostgreSQL>, the old
+ savepoint is kept, though only the last one will be used when rolling back or
+ releasing. Other than that, SAVEPOINT is fully SQL conforming.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+
+
+
diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/ref/start_transaction.sgml 04pgproc/doc/src/sgml/ref/start_transaction.sgml
*** 00orig/doc/src/sgml/ref/start_transaction.sgml 2004-01-11 02:46:58.000000000 -0300
--- 04pgproc/doc/src/sgml/ref/start_transaction.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 66,71 ****
--- 66,72 ----
+
diff -Ncr --exclude-from=diff-ignore 00orig/doc/src/sgml/reference.sgml 04pgproc/doc/src/sgml/reference.sgml
*** 00orig/doc/src/sgml/reference.sgml 2004-06-26 00:28:44.000000000 -0400
--- 04pgproc/doc/src/sgml/reference.sgml 2004-07-27 10:29:09.000000000 -0400
***************
*** 120,128 ****
--- 120,131 ----
¬ify;
&prepare;
&reindex;
+ &releaseSavepoint;
&reset;
&revoke;
&rollback;
+ &rollbackTo;
+ &savepoint;
&select;
&selectInto;
&set;
diff -Ncr --exclude-from=diff-ignore 00orig/src/backend/access/transam/README 04pgproc/src/backend/access/transam/README
*** 00orig/src/backend/access/transam/README 1969-12-31 21:00:00.000000000 -0300
--- 04pgproc/src/backend/access/transam/README 2004-07-28 19:18:11.000000000 -0400
***************
*** 0 ****
--- 1,224 ----
+ The Transaction System
+ ======================
+
+ xact.c
+ ------
+
+ PostgreSQL's transaction system is a three-layer system, implementing
+ low-level transactions and subtransactions, on top of which rests the
+ mainloop's control code, which in turn implements user-visible transactions
+ and savepoints.
+
+ The middle layer of code is called by postgres.c before and after the
+ processing of each query,
+
+ StartTransactionCommand
+ CommitTransactionCommand
+ AbortCurrentTransaction
+
+ Meanwhile, the user can alter the system's state by issuing the SQL commands
+ BEGIN, ROLLBACK, SAVEPOINT or RELEASE. The traffic cop redirects these calls
+ to the toplevel routines
+
+ BeginTransactionBlock
+ EndTransactionBlock
+ UserAbortTransactionBlock
+ DefineSavepoint
+ RollbackToSavepoint
+ ReleaseSavepoint
+ RollbackAndReleaseSavepoint
+
+ Depending on the current state of the system, these function call low level
+ functions to activate the real transaction system,
+
+ StartTransaction
+ CommitTransaction
+ AbortTransaction
+ CleanupTransaction
+ StartSubTransaction
+ CommitSubTransaction
+ AbortSubTransaction
+ CleanupSubTransaction
+
+ Additionally, within a transaction, CommandCounterIncrement is called to
+ increment the command counter, which allows future commands to "see" the
+ effects of previous commands within the same transaction. Note that this is
+ done automatically by CommitTransactionCommand after each query inside a
+ transaction block, but some utility functions also do it to allow some
+ operations (usually in the system catalogs) to be seen by future operations in
+ the same utility command processing (for example, in DefineRelation it is done
+ after creating the heap so the pg_class row is visible, to be able to lock
+ it).
+
+
+ For example, consider the following sequence of user commands:
+
+ 1) BEGIN
+ 2) SELECT * FROM foo
+ 3) INSERT INTO foo VALUES (...)
+ 4) COMMIT
+
+ In the main processing loop, this results in the following function call
+ sequence:
+
+ / StartTransactionCommand;
+ / ProcessUtility; << BEGIN
+ 1) < BeginTransactionBlock;
+ \ StartTransaction;
+ \ CommitTransactionCommand;
+
+ / StartTransactionCommand;
+ 2) / ProcessQuery; << SELECT * FROM foo
+ \ CommitTransactionCommand;
+ \ CommandCounterIncrement;
+
+ / StartTransactionCommand;
+ 3) / ProcessQuery; << INSERT INTO foo VALUES (...)
+ \ CommitTransactionCommand;
+ \ CommandCounterIncrement;
+
+ / StartTransactionCommand;
+ / ProcessUtility; << COMMIT
+ 4) < EndTransactionBlock;
+ \ CommitTransaction;
+ \ CommitTransactionCommand;
+
+ The point of this example is to demonstrate the need for
+ StartTransactionCommand and CommitTransactionCommand to be state smart -- they
+ should do nothing in between the calls to BeginTransactionBlock and
+ EndTransactionBlock and outside these calls they need to do normal start,
+ commit or abort processing.
+
+ Furthermore, suppose the "SELECT * FROM foo" caused an abort condition. In
+ this case AbortCurrentTransaction is called, and the transaction is put in
+ aborted state. In this state, any user input is ignored except for
+ transaction-termination statements, or ROLLBACK TO commands.
+
+ Transaction aborts can occur in two ways:
+
+ 1) system dies from some internal cause (syntax error, etc)
+ 2) user types ROLLBACK
+
+ The reason we have to distinguish them is illustrated by the following two
+ situations:
+
+ case 1 case 2
+ ------ ------
+ 1) user types BEGIN 1) user types BEGIN
+ 2) user does something 2) user does something
+ 3) user does not like what 3) system aborts for some reason
+ she sees and types ABORT (syntax error, etc)
+
+ In case 1, we want to abort the transaction and return to the default state.
+ In case 2, there may be more commands coming our way which are part of the
+ same transaction block; we have to ignore these commands until we see a COMMIT
+ or ROLLBACK.
+
+ Internal aborts are handled by AbortCurrentTransaction, while user aborts are
+ handled by UserAbortTransactionBlock. Both of them rely on AbortTransaction
+ to do all the real work. The only difference is what state we enter after
+ AbortTransaction does its work:
+
+ * AbortCurrentTransaction leaves us in TBLOCK_ABORT,
+ * UserAbortTransactionBlock leaves us in TBLOCK_ENDABORT
+
+ Low-level transaction abort handling is divided in two phases:
+ * AbortTransaction executes as soon as we realize the transaction has
+ failed. It should release all shared resources (locks etc) so that we do
+ not delay other backends unnecessarily.
+ * CleanupTransaction executes when we finally see a user COMMIT
+ or ROLLBACK command; it cleans things up and gets us out of the transaction
+ internally. In particular, we mustn't destroy TopTransactionContext until
+ this point.
+
+ Also, note that when a transaction is committed, we don't close it right away.
+ Rather it's put in TBLOCK_END state, which means that when
+ CommitTransactionCommand is called after the query has finished processing,
+ the transaction has to be closed. The distinction is subtle but important,
+ because it means that control will leave the xact.c code with the transaction
+ open, and the main loop will be able to keep processing inside the same
+ transaction. So, in a sense, transaction commit is also handled in two
+ phases, the first at EndTransactionBlock and the second at
+ CommitTransactionCommand.
+
+ The rest of the code in xact.c are routines to support the creation and
+ finishing of transactions and subtransactions. For example, AtStart_Memory
+ takes care of initializing the memory subsystem at main transaction start.
+
+
+ Subtransaction handling
+ -----------------------
+
+ Subtransactions are implemented using a stack of TransactionState structures,
+ which has a pointer to its parent transaction. When a new subtransaction is
+ to be opened, PushTransaction is called, which creates a new TransactionState,
+ with its parent pointing to the current transaction. StartSubTransaction is
+ in charge of initializing the new TransactionState to sane values, and
+ properly initializing other subsystems.
+
+ When closing a subtransaction, either CommitSubTransaction has to be called
+ (if the subtransaction is committing), or AbortSubTransaction and
+ CleanupSubTransaction (if it's aborting). In either case, PopTransaction is
+ called so the system returns to the parent transaction.
+
+ One important point regarding subtransaction handling is that several may need
+ to be closed in response to a single user command. That's because savepoints
+ have names, and we allow to commit or rollback a savepoint by name, which not
+ necessarily is the one that was last opened. In the case of subtransaction
+ commit this is not a problem, and we close all the involved subtransactions
+ right away by calling CommitTransactionToLevel, which in turn calls
+ CommitSubTransaction and PopTransaction as many times as needed.
+
+ In the case of subtransaction abort (when the user issues ROLLBACK TO
+ ), things are not so easy. We have to keep the subtransactions
+ open and return control to the main loop. So what RollbackToSavepoint does is
+ abort the innermost subtransaction and put it in TBLOCK_SUBENDABORT state, and
+ put the rest in TBLOCK_SUBABORT_PENDING state. Then we return control to the
+ main loop, which will in turn return control to us by calling
+ CommitTransactionCommand. At this point we can close all subtransactions that
+ are marked with the "abort pending" state.
+
+
+
+ pg_clog and pg_subtrans
+ -----------------------
+
+ pg_clog and pg_subtrans are permanent (on-disk) storage of transaction related
+ information. There is a limited number of pages of each kept in memory, so
+ in many cases there is no need to actually read from disk. However, if
+ there's a long running transaction or a backend sitting idle with an open
+ transaction, it's necessary to be able to read and write this information from
+ disk. They also allow information to be permanent across server restarts.
+
+ pg_clog records the commit status for each transaction. A transaction can be
+ in progress, committed, aborted, or "sub-committed". This last state means
+ that it's a subtransaction that's no longer running, but it's parent has not
+ updated its state yet (either it is still running, or the backend crashed
+ without updating its status). A sub-committed transaction's status will be
+ updated again to the final value as soon as the parent commits or aborts, or
+ when the parent is detected to be aborted.
+
+ Savepoints are implemented using subtransactions. A subtransaction is a
+ transaction inside a transaction; it gets its own TransactionId, but its
+ commit or abort status is not only dependent on whether it committed itself,
+ but also whether its parent transaction committed. To implement multiple
+ savepoints in a transaction we allow unlimited transaction nesting depth, so
+ any particular subtransaction's commit state is dependent on the commit status
+ of each and every ancestor transaction.
+
+ The "subtransaction parent" (pg_subtrans) mechanism records, for each
+ transaction, the TransactionId of its parent transaction. This information is
+ stored as soon as the subtransaction is created.
+
+ pg_subtrans is used to know whether the transaction in question is still
+ running --- the main Xid of a transaction is recorded in the PGPROC struct,
+ but since we allow arbitrary nesting of subtransactions, we can't fit all Xids
+ in shared memory, so we have to store them on disk. Note, however, that for
+ each transaction we keep a "cache" of Xids that are known to be part of the
+ transaction tree, so we can skip looking at pg_subtrans unless we know the
+ cache has been overflowed. See storage/ipc/sinval.c for the gory details.
+
+ slru.c is the supporting mechanism for both pg_clog and pg_subtrans. It
+ implements the LRU policy for in-memory buffer pages. The high-level routines
+ for pg_clog are implemented transam.c, while the low-level functions are in
+ clog.c. pg_subtrans is contained completely in subtrans.c.
diff -Ncr --exclude-from=diff-ignore 00orig/src/backend/access/transam/transam.c 04pgproc/src/backend/access/transam/transam.c
*** 00orig/src/backend/access/transam/transam.c 2004-06-30 22:46:48.000000000 -0400
--- 04pgproc/src/backend/access/transam/transam.c 2004-07-31 19:06:06.708810182 -0400
***************
*** 253,259 ****
{
TransactionId parentXid;
bool parentAborted;
!
parentXid = SubTransGetParent(transactionId);
parentAborted = TransactionIdDidAbort(parentXid);
--- 253,259 ----
{
TransactionId parentXid;
bool parentAborted;
!
parentXid = SubTransGetParent(transactionId);
parentAborted = TransactionIdDidAbort(parentXid);
diff -Ncr --exclude-from=diff-ignore 00orig/src/backend/access/transam/xact.c 04pgproc/src/backend/access/transam/xact.c
*** 00orig/src/backend/access/transam/xact.c 2004-07-31 11:22:26.000000000 -0400
--- 04pgproc/src/backend/access/transam/xact.c 2004-07-31 18:28:30.332426496 -0400
***************
*** 10,141 ****
* IDENTIFICATION
* $PostgreSQL: pgsql-server/src/backend/access/transam/xact.c,v 1.173 2004/07/28 14:23:27 tgl Exp $
*
- * NOTES
- * Transaction aborts can now occur two ways:
- *
- * 1) system dies from some internal cause (syntax error, etc..)
- * 2) user types ABORT
- *
- * These two cases used to be treated identically, but now
- * we need to distinguish them. Why? consider the following
- * two situations:
- *
- * case 1 case 2
- * ------ ------
- * 1) user types BEGIN 1) user types BEGIN
- * 2) user does something 2) user does something
- * 3) user does not like what 3) system aborts for some reason
- * she sees and types ABORT
- *
- * In case 1, we want to abort the transaction and return to the
- * default state. In case 2, there may be more commands coming
- * our way which are part of the same transaction block and we have
- * to ignore these commands until we see a COMMIT transaction or
- * ROLLBACK.
- *
- * Internal aborts are now handled by AbortTransactionBlock(), just as
- * they always have been, and user aborts are now handled by
- * UserAbortTransactionBlock(). Both of them rely on AbortTransaction()
- * to do all the real work. The only difference is what state we
- * enter after AbortTransaction() does its work:
- *
- * * AbortTransactionBlock() leaves us in TBLOCK_ABORT and
- * * UserAbortTransactionBlock() leaves us in TBLOCK_ENDABORT
- *
- * Low-level transaction abort handling is divided into two phases:
- * * AbortTransaction() executes as soon as we realize the transaction
- * has failed. It should release all shared resources (locks etc)
- * so that we do not delay other backends unnecessarily.
- * * CleanupTransaction() executes when we finally see a user COMMIT
- * or ROLLBACK command; it cleans things up and gets us out of
- * the transaction internally. In particular, we mustn't destroy
- * TopTransactionContext until this point.
- *
- * NOTES
- * The essential aspects of the transaction system are:
- *
- * o transaction id generation
- * o transaction log updating
- * o memory cleanup
- * o cache invalidation
- * o lock cleanup
- *
- * Hence, the functional division of the transaction code is
- * based on which of the above things need to be done during
- * a start/commit/abort transaction. For instance, the
- * routine AtCommit_Memory() takes care of all the memory
- * cleanup stuff done at commit time.
- *
- * The code is layered as follows:
- *
- * StartTransaction
- * CommitTransaction
- * AbortTransaction
- * CleanupTransaction
- *
- * are provided to do the lower level work like recording
- * the transaction status in the log and doing memory cleanup.
- * above these routines are another set of functions:
- *
- * StartTransactionCommand
- * CommitTransactionCommand
- * AbortCurrentTransaction
- *
- * These are the routines used in the postgres main processing
- * loop. They are sensitive to the current transaction block state
- * and make calls to the lower level routines appropriately.
- *
- * Support for transaction blocks is provided via the functions:
- *
- * BeginTransactionBlock
- * CommitTransactionBlock
- * AbortTransactionBlock
- *
- * These are invoked only in response to a user "BEGIN WORK", "COMMIT",
- * or "ROLLBACK" command. The tricky part about these functions
- * is that they are called within the postgres main loop, in between
- * the StartTransactionCommand() and CommitTransactionCommand().
- *
- * For example, consider the following sequence of user commands:
- *
- * 1) begin
- * 2) select * from foo
- * 3) insert into foo (bar = baz)
- * 4) commit
- *
- * in the main processing loop, this results in the following
- * transaction sequence:
- *
- * / StartTransactionCommand();
- * 1) / ProcessUtility(); << begin
- * \ BeginTransactionBlock();
- * \ CommitTransactionCommand();
- *
- * / StartTransactionCommand();
- * 2) < ProcessQuery(); << select * from foo
- * \ CommitTransactionCommand();
- *
- * / StartTransactionCommand();
- * 3) < ProcessQuery(); << insert into foo (bar = baz)
- * \ CommitTransactionCommand();
- *
- * / StartTransactionCommand();
- * 4) / ProcessUtility(); << commit
- * \ CommitTransactionBlock();
- * \ CommitTransactionCommand();
- *
- * The point of this example is to demonstrate the need for
- * StartTransactionCommand() and CommitTransactionCommand() to
- * be state smart -- they should do nothing in between the calls
- * to BeginTransactionBlock() and EndTransactionBlock() and
- * outside these calls they need to do normal start/commit
- * processing.
- *
- * Furthermore, suppose the "select * from foo" caused an abort
- * condition. We would then want to abort the transaction and
- * ignore all subsequent commands up to the "commit".
- * -cim 3/23/90
- *
*-------------------------------------------------------------------------
*/
--- 10,15 ----
***************
*** 239,245 ****
static void AtStart_Cache(void);
static void AtStart_Memory(void);
static void AtStart_ResourceOwner(void);
! static void CallEOXactCallbacks(bool isCommit);
static void CleanupTransaction(void);
static void CommitTransaction(void);
static void RecordTransactionAbort(void);
--- 113,119 ----
static void AtStart_Cache(void);
static void AtStart_Memory(void);
static void AtStart_ResourceOwner(void);
! static void CallXactCallbacks(XactEvent event, TransactionId parentXid);
static void CleanupTransaction(void);
static void CommitTransaction(void);
static void RecordTransactionAbort(void);
***************
*** 315,330 ****
/*
! * List of add-on end-of-xact callbacks
*/
! typedef struct EOXactCallbackItem
{
! struct EOXactCallbackItem *next;
! EOXactCallback callback;
void *arg;
! } EOXactCallbackItem;
! static EOXactCallbackItem *EOXact_callbacks = NULL;
static void (*_RollbackFunc) (void *) = NULL;
static void *_RollbackData = NULL;
--- 189,204 ----
/*
! * List of add-on start- and end-of-xact callbacks
*/
! typedef struct XactCallbackItem
{
! struct XactCallbackItem *next;
! XactCallback callback;
void *arg;
! } XactCallbackItem;
! static XactCallbackItem *Xact_callbacks = NULL;
static void (*_RollbackFunc) (void *) = NULL;
static void *_RollbackData = NULL;
***************
*** 490,496 ****
return true;
foreach(cell, s->childXids)
{
! if (TransactionIdEquals(xid, lfirst_int(cell)))
return true;
}
--- 364,370 ----
return true;
foreach(cell, s->childXids)
{
! if (TransactionIdEquals(xid, lfirst_xid(cell)))
return true;
}
***************
*** 880,886 ****
s->parent->childXids = list_concat(s->parent->childXids, s->childXids);
s->childXids = NIL; /* ensure list not doubly referenced */
! s->parent->childXids = lappend_int(s->parent->childXids,
s->transactionIdData);
MemoryContextSwitchTo(old_cxt);
--- 754,760 ----
s->parent->childXids = list_concat(s->parent->childXids, s->childXids);
s->childXids = NIL; /* ensure list not doubly referenced */
! s->parent->childXids = lappend_xid(s->parent->childXids,
s->transactionIdData);
MemoryContextSwitchTo(old_cxt);
***************
*** 1020,1025 ****
--- 894,901 ----
TransactionIdAbortTree(nchildren, children);
TransactionIdAbort(xid);
+ XidCacheClean();
+
END_CRIT_SECTION();
}
***************
*** 1159,1164 ****
--- 1035,1042 ----
TransactionIdAbortTree(nchildren, children);
TransactionIdAbort(xid);
+ XidCacheRemoveRunningXids(nchildren, children, xid);
+
END_CRIT_SECTION();
}
***************
*** 1390,1395 ****
--- 1268,1278 ----
MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
LWLockRelease(SInvalLock);
+
+ /*
+ * Clean up the Xid cache.
+ */
+ XidCacheClean();
}
/*
***************
*** 1431,1437 ****
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);
! CallEOXactCallbacks(true);
AtEOXact_GUC(true, false);
AtEOXact_SPI(true);
AtEOXact_on_commit_actions(true, s->transactionIdData);
--- 1314,1320 ----
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);
! CallXactCallbacks(XACT_EVENT_COMMIT, InvalidTransactionId);
AtEOXact_GUC(true, false);
AtEOXact_SPI(true);
AtEOXact_on_commit_actions(true, s->transactionIdData);
***************
*** 1562,1574 ****
RESOURCE_RELEASE_AFTER_LOCKS,
false, true);
! CallEOXactCallbacks(false);
AtEOXact_GUC(false, false);
AtEOXact_SPI(false);
AtEOXact_on_commit_actions(false, s->transactionIdData);
AtEOXact_Namespace(false);
AtEOXact_Files();
! SetReindexProcessing(InvalidOid, InvalidOid);
pgstat_count_xact_rollback();
/*
--- 1445,1457 ----
RESOURCE_RELEASE_AFTER_LOCKS,
false, true);
! CallXactCallbacks(XACT_EVENT_ABORT, InvalidTransactionId);
AtEOXact_GUC(false, false);
AtEOXact_SPI(false);
AtEOXact_on_commit_actions(false, s->transactionIdData);
AtEOXact_Namespace(false);
AtEOXact_Files();
! ResetReindexProcessing(true);
pgstat_count_xact_rollback();
/*
***************
*** 2158,2200 ****
/*
! * Register or deregister callback functions for end-of-xact cleanup
*
* These functions are intended for use by dynamically loaded modules.
* For built-in modules we generally just hardwire the appropriate calls
* (mainly because it's easier to control the order that way, where needed).
*
! * Note that the callback occurs post-commit or post-abort, so the callback
! * functions can only do noncritical cleanup.
*/
void
! RegisterEOXactCallback(EOXactCallback callback, void *arg)
{
! EOXactCallbackItem *item;
! item = (EOXactCallbackItem *)
! MemoryContextAlloc(TopMemoryContext, sizeof(EOXactCallbackItem));
item->callback = callback;
item->arg = arg;
! item->next = EOXact_callbacks;
! EOXact_callbacks = item;
}
void
! UnregisterEOXactCallback(EOXactCallback callback, void *arg)
{
! EOXactCallbackItem *item;
! EOXactCallbackItem *prev;
prev = NULL;
! for (item = EOXact_callbacks; item; prev = item, item = item->next)
{
if (item->callback == callback && item->arg == arg)
{
if (prev)
prev->next = item->next;
else
! EOXact_callbacks = item->next;
pfree(item);
break;
}
--- 2041,2086 ----
/*
! * Register or deregister callback functions for start- and end-of-xact
! * operations.
*
* These functions are intended for use by dynamically loaded modules.
* For built-in modules we generally just hardwire the appropriate calls
* (mainly because it's easier to control the order that way, where needed).
*
! * At transaction end, the callback occurs post-commit or post-abort, so the
! * callback functions can only do noncritical cleanup. At subtransaction
! * start, the callback is called when the subtransaction has finished
! * initializing.
*/
void
! RegisterXactCallback(XactCallback callback, void *arg)
{
! XactCallbackItem *item;
! item = (XactCallbackItem *)
! MemoryContextAlloc(TopMemoryContext, sizeof(XactCallbackItem));
item->callback = callback;
item->arg = arg;
! item->next = Xact_callbacks;
! Xact_callbacks = item;
}
void
! UnregisterXactCallback(XactCallback callback, void *arg)
{
! XactCallbackItem *item;
! XactCallbackItem *prev;
prev = NULL;
! for (item = Xact_callbacks; item; prev = item, item = item->next)
{
if (item->callback == callback && item->arg == arg)
{
if (prev)
prev->next = item->next;
else
! Xact_callbacks = item->next;
pfree(item);
break;
}
***************
*** 2202,2214 ****
}
static void
! CallEOXactCallbacks(bool isCommit)
{
! EOXactCallbackItem *item;
! for (item = EOXact_callbacks; item; item = item->next)
{
! (*item->callback) (isCommit, item->arg);
}
}
--- 2088,2100 ----
}
static void
! CallXactCallbacks(XactEvent event, TransactionId parentXid)
{
! XactCallbackItem *item;
! for (item = Xact_callbacks; item; item = item->next)
{
! (*item->callback) (event, parentXid, item->arg);
}
}
***************
*** 2948,2979 ****
IsSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
-
- switch (s->blockState)
- {
- case TBLOCK_DEFAULT:
- case TBLOCK_STARTED:
- case TBLOCK_BEGIN:
- case TBLOCK_INPROGRESS:
- case TBLOCK_END:
- case TBLOCK_ABORT:
- case TBLOCK_ENDABORT:
- return false;
- case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBINPROGRESS:
- case TBLOCK_SUBABORT:
- case TBLOCK_SUBEND:
- case TBLOCK_SUBENDABORT_ALL:
- case TBLOCK_SUBENDABORT:
- case TBLOCK_SUBABORT_PENDING:
- case TBLOCK_SUBENDABORT_RELEASE:
- return true;
- }
! /* should never get here */
! elog(FATAL, "invalid transaction block state: %s",
! BlockStateAsString(s->blockState));
! return false; /* keep compiler quiet */
}
/*
--- 2834,2844 ----
IsSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
! if (s->nestingLevel >= 2)
! return true;
!
! return false;
}
/*
***************
*** 3006,3011 ****
--- 2871,2883 ----
XactLockTableInsert(s->transactionIdData);
/*
+ * Ideally, we would only cache Xids of subtransactions that write tuples
+ * in permanent storage. We have no clean way of knowing that, however
+ * (much less in advance ...)
+ */
+ XidCacheAddRunningXid(s->transactionIdData);
+
+ /*
* Finish setup of other transaction state fields.
*/
s->currentUser = GetUserId();
***************
*** 3020,3025 ****
--- 2892,2902 ----
s->state = TRANS_INPROGRESS;
+ /*
+ * Call start-of-subxact callbacks
+ */
+ CallXactCallbacks(XACT_EVENT_START_SUB, s->parent->transactionIdData);
+
ShowTransactionState("StartSubTransaction");
}
***************
*** 3037,3047 ****
elog(WARNING, "CommitSubTransaction while in %s state",
TransStateAsString(s->state));
! /* Pre-commit processing */
! AtSubCommit_Portals(s->parent->transactionIdData,
! s->parent->curTransactionOwner);
! DeferredTriggerEndSubXact(true);
!
s->state = TRANS_COMMIT;
/* Mark subtransaction as subcommitted */
--- 2914,2920 ----
elog(WARNING, "CommitSubTransaction while in %s state",
TransStateAsString(s->state));
! /* Pre-commit processing -- nothing to do at the moment */
s->state = TRANS_COMMIT;
/* Mark subtransaction as subcommitted */
***************
*** 3050,3068 ****
AtSubCommit_childXids();
/* Post-commit cleanup */
! AtSubCommit_smgr();
!
! AtEOSubXact_Inval(true);
! AtEOSubXact_SPI(true, s->transactionIdData);
!
AtEOSubXact_LargeObject(true, s->transactionIdData,
s->parent->transactionIdData);
AtEOSubXact_UpdatePasswordFile(true, s->transactionIdData,
s->parent->transactionIdData);
! AtEOSubXact_Files(true, s->transactionIdData,
! s->parent->transactionIdData);
! AtEOSubXact_Namespace(true, s->transactionIdData,
! s->parent->transactionIdData);
/*
* Note that we just release the resource owner's resources and don't
--- 2923,2936 ----
AtSubCommit_childXids();
/* Post-commit cleanup */
! AtSubCommit_Portals(s->parent->transactionIdData,
! s->parent->curTransactionOwner);
AtEOSubXact_LargeObject(true, s->transactionIdData,
s->parent->transactionIdData);
+ AtSubCommit_Notify();
AtEOSubXact_UpdatePasswordFile(true, s->transactionIdData,
s->parent->transactionIdData);
! AtSubCommit_smgr();
/*
* Note that we just release the resource owner's resources and don't
***************
*** 3074,3088 ****
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, false);
/* we can skip the LOCKS phase */
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, false);
! AtSubCommit_Notify();
AtEOXact_GUC(true, true);
AtEOSubXact_on_commit_actions(true, s->transactionIdData,
s->parent->transactionIdData);
/*
* We need to restore the upper transaction's read-only state,
--- 2942,2963 ----
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, false);
+ AtEOSubXact_Inval(true);
/* we can skip the LOCKS phase */
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, false);
! CallXactCallbacks(XACT_EVENT_COMMIT_SUB, s->parent->transactionIdData);
AtEOXact_GUC(true, true);
+ AtEOSubXact_SPI(true, s->transactionIdData);
AtEOSubXact_on_commit_actions(true, s->transactionIdData,
s->parent->transactionIdData);
+ AtEOSubXact_Namespace(true, s->transactionIdData,
+ s->parent->transactionIdData);
+ AtEOSubXact_Files(true, s->transactionIdData,
+ s->parent->transactionIdData);
+ DeferredTriggerEndSubXact(true);
/*
* We need to restore the upper transaction's read-only state,
***************
*** 3134,3168 ****
LockWaitCancel();
- AtSubAbort_Memory();
-
/*
* do abort processing
*/
!
! RecordSubTransactionAbort();
!
! /* Post-abort cleanup */
! AtSubAbort_smgr();
DeferredTriggerEndSubXact(false);
- AtEOSubXact_SPI(false, s->transactionIdData);
AtSubAbort_Portals(s->parent->transactionIdData,
s->parent->curTransactionOwner);
- AtEOSubXact_Inval(false);
-
AtEOSubXact_LargeObject(false, s->transactionIdData,
s->parent->transactionIdData);
AtEOSubXact_UpdatePasswordFile(false, s->transactionIdData,
s->parent->transactionIdData);
! AtEOSubXact_Files(false, s->transactionIdData,
! s->parent->transactionIdData);
! AtEOSubXact_Namespace(false, s->transactionIdData,
! s->parent->transactionIdData);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, false);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_LOCKS,
false, false);
--- 3009,3038 ----
LockWaitCancel();
/*
* do abort processing
*/
! AtSubAbort_Memory();
DeferredTriggerEndSubXact(false);
AtSubAbort_Portals(s->parent->transactionIdData,
s->parent->curTransactionOwner);
AtEOSubXact_LargeObject(false, s->transactionIdData,
s->parent->transactionIdData);
+ AtSubAbort_Notify();
AtEOSubXact_UpdatePasswordFile(false, s->transactionIdData,
s->parent->transactionIdData);
!
! /* Advertise the fact that we aborted in pg_clog. */
! RecordSubTransactionAbort();
!
! /* Post-abort cleanup */
! AtSubAbort_smgr();
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, false);
+ AtEOSubXact_Inval(false);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_LOCKS,
false, false);
***************
*** 3170,3179 ****
RESOURCE_RELEASE_AFTER_LOCKS,
false, false);
! AtSubAbort_Notify();
AtEOXact_GUC(false, true);
AtEOSubXact_on_commit_actions(false, s->transactionIdData,
s->parent->transactionIdData);
/*
* Reset user id which might have been changed transiently. Here we
--- 3040,3055 ----
RESOURCE_RELEASE_AFTER_LOCKS,
false, false);
! CallXactCallbacks(XACT_EVENT_ABORT_SUB, s->parent->transactionIdData);
AtEOXact_GUC(false, true);
+ AtEOSubXact_SPI(false, s->transactionIdData);
AtEOSubXact_on_commit_actions(false, s->transactionIdData,
s->parent->transactionIdData);
+ AtEOSubXact_Namespace(false, s->transactionIdData,
+ s->parent->transactionIdData);
+ AtEOSubXact_Files(false, s->transactionIdData,
+ s->parent->transactionIdData);
+ ResetReindexProcessing(true);
/*
* Reset user id which might have been changed transiently. Here we
***************
*** 3196,3203 ****
*/
XactReadOnly = s->prevXactReadOnly;
- CommandCounterIncrement();
-
RESUME_INTERRUPTS();
}
--- 3072,3077 ----
***************
*** 3481,3487 ****
foreach(p, s->childXids)
{
! TransactionId child = lfirst_int(p);
*children++ = child;
}
--- 3355,3361 ----
foreach(p, s->childXids)
{
! TransactionId child = lfirst_xid(p);
*children++ = child;
}
diff -Ncr --exclude-from=diff-ignore 00orig/src/backend/catalog/index.c 04pgproc/src/backend/catalog/index.c
*** 00orig/src/backend/catalog/index.c 2004-07-30 20:55:38.000000000 -0400
--- 04pgproc/src/backend/catalog/index.c 2004-07-30 20:41:37.000000000 -0400
***************
*** 1719,1725 ****
* index_build will close both the heap and index relations (but not
* give up the locks we hold on them). So we're done.
*/
! SetReindexProcessing(InvalidOid, InvalidOid);
}
/*
--- 1719,1725 ----
* index_build will close both the heap and index relations (but not
* give up the locks we hold on them). So we're done.
*/
! ResetReindexProcessing(false);
}
/*
Los ficheros 00orig/src/backend/po/messages.mo y 04pgproc/src/backend/po/messages.mo son distintos
diff -Ncr --exclude-from=diff-ignore 00orig/src/backend/storage/ipc/sinval.c 04pgproc/src/backend/storage/ipc/sinval.c
*** 00orig/src/backend/storage/ipc/sinval.c 2004-06-30 23:50:07.000000000 -0400
--- 04pgproc/src/backend/storage/ipc/sinval.c 2004-07-31 19:03:47.982829645 -0400
***************
*** 27,32 ****
--- 27,53 ----
#include "utils/tqual.h"
#include "miscadmin.h"
+ #ifdef XIDCACHE_DEBUG
+ static void
+ DisplayXidCache(int code, Datum arg);
+
+ /* counters for XidCache measurement */
+ static int xc_by_recent_xmin = 0;
+ static int xc_by_main_xid = 0;
+ static int xc_by_child_xid = 0;
+ static int xc_slow_answer = 0;
+ #define xc_by_recent_xmin_inc xc_by_recent_xmin++
+ #define xc_by_main_xid_inc xc_by_main_xid++
+ #define xc_by_child_xid_inc xc_by_child_xid++
+ #define xc_slow_answer_inc xc_slow_answer++
+
+ #else /* XIDCACHE_DEBUG */
+
+ #define xc_by_recent_xmin_inc
+ #define xc_by_main_xid_inc
+ #define xc_by_child_xid_inc
+ #define xc_slow_answer_inc
+ #endif /* XIDCACHE_DEBUG */
/*
* Because backends sitting idle will not be reading sinval events, we
***************
*** 80,85 ****
--- 101,110 ----
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("sorry, too many clients already")));
+
+ #ifdef XIDCACHE_DEBUG
+ on_proc_exit(DisplayXidCache, (Datum) NULL);
+ #endif /* XIDCACHE_DEBUG */
}
/*
***************
*** 444,451 ****
*
* SInvalLock has to be held while we do 1 and 2. If we save all the Xids
* while doing 1, we can release the SInvalLock while we do 3. This buys back
! * some concurrency (we can't retrieve the main Xids from PGPROC again anyway,
! * see GetNewTransactionId)
*/
bool
TransactionIdIsInProgress(TransactionId xid)
--- 469,476 ----
*
* SInvalLock has to be held while we do 1 and 2. If we save all the Xids
* while doing 1, we can release the SInvalLock while we do 3. This buys back
! * some concurrency (we can't retrieve the main Xids from PGPROC again anyway;
! * see GetNewTransactionId).
*/
bool
TransactionIdIsInProgress(TransactionId xid)
***************
*** 453,465 ****
bool result = false;
SISeg *segP = shmInvalBuffer;
ProcState *stateP = segP->procState;
! int i;
! int nxids = 0;
TransactionId *xids;
! xids = (TransactionId *)palloc(sizeof(TransactionId) * segP->maxBackends);
LWLockAcquire(SInvalLock, LW_SHARED);
for (i = 0; i < segP->lastBackend; i++)
{
--- 478,502 ----
bool result = false;
SISeg *segP = shmInvalBuffer;
ProcState *stateP = segP->procState;
! int i,
! j;
TransactionId *xids;
+ bool locked;
+ bool overflowed = false;
+
+ /*
+ * Don't bother checking a very old transaction.
+ */
+ if (TransactionIdPrecedes(xid, RecentGlobalXmin))
+ {
+ xc_by_recent_xmin_inc;
+ return false;
+ }
! xids = (TransactionId *) palloc(sizeof(TransactionId) * segP->maxBackends);
LWLockAcquire(SInvalLock, LW_SHARED);
+ locked = true;
for (i = 0; i < segP->lastBackend; i++)
{
***************
*** 473,545 ****
TransactionId pxid = proc->xid;
/*
! * check the main Xid (step 1 above)
*/
if (TransactionIdEquals(pxid, xid))
{
result = true;
! break;
}
! /*
! * save the main Xid for step 3.
! */
! xids[nxids++] = pxid;
! #ifdef NOT_USED
! FIXME -- waiting to save the Xids in PGPROC ...
/*
! * check the saved Xids array (step 2)
*/
! for (j = 0; j < PGPROC_MAX_SAVED_XIDS; j++)
{
! pxid = proc->savedxids[j];
! if (!TransactionIdIsValid(pxids))
! break;
if (TransactionIdEquals(pxid, xid))
{
result = true;
! break;
}
}
- #endif
-
- if (result)
- break;
}
}
LWLockRelease(SInvalLock);
/*
* Step 3: have to check pg_subtrans. Use the saved Xids.
*
! * XXX Could save the cached Xids too for further improvement.
*/
! if (!result)
{
! /* this is a potentially expensive call. */
! xid = SubTransGetTopmostTransaction(xid);
!
! Assert(TransactionIdIsValid(xid));
! /*
! * We don't care if it aborted, because if it did, we won't find
! * it in the array.
! */
! for (i = 0; i < nxids; i++)
{
! if (TransactionIdEquals(xids[i], xid))
! {
! result = true;
! break;
! }
}
}
pfree(xids);
return result;
--- 510,629 ----
TransactionId pxid = proc->xid;
/*
! * Step 1: check the main Xid
*/
if (TransactionIdEquals(pxid, xid))
{
+ xc_by_main_xid_inc;
result = true;
! goto result_known;
}
! /* save the main Xid for step 3. */
! xids[i] = pxid;
! if (proc->cache.overflow)
! overflowed = true;
/*
! * Step 2: check the cached Xids arrays
*/
! for (j = 0; j < PGPROC_MAX_CACHED_SUBXIDS; j++)
{
! pxid = proc->cache.xids[j];
! if (!TransactionIdIsValid(pxid))
! continue;
if (TransactionIdEquals(pxid, xid))
{
+ xc_by_child_xid_inc;
result = true;
! goto result_known;
}
}
}
}
LWLockRelease(SInvalLock);
+ locked = false;
+
+ /*
+ * If none of the caches overflowed, we know the Xid is
+ * not running without looking at pg_subtrans.
+ */
+ if (!overflowed)
+ goto result_known;
/*
* Step 3: have to check pg_subtrans. Use the saved Xids.
+ */
+ xc_slow_answer_inc;
+
+ /*
+ * At this point, we know it's either a subtransaction or
+ * it's not running. If it's a subtransaction, we have to
+ * check whether it's part of a running subtransaction tree
+ * or it was aborted. So we have to look at pg_clog, but
+ * since we already checked the PGPROC array we don't have to
+ * worry about a race condition.
+ */
+ if (TransactionIdDidAbort(xid))
+ {
+ result = false;
+ goto result_known;
+ }
+
+ /*
+ * It isn't aborted, so check whether the transaction tree it
+ * belongs to is still running (or, more precisely, whether it
+ * was running when this routine started -- note that we just
+ * released SInvalLock.)
+ */
+ xid = SubTransGetTopmostTransaction(xid);
+ Assert(TransactionIdIsValid(xid));
+
+ for (i = 0; i < segP->maxBackends; i++)
+ {
+ if (TransactionIdEquals(xids[i], xid))
+ {
+ result = true;
+ break;
+ }
+ }
+
+ /*
+ * pg_subtrans says it's running in the i-nd backend. Check if it's
+ * still true.
*
! * This is strictly not needed, but 1) an eternity has passed since
! * the main Xid was read (we took a peek at both pg_clog and pg_subtrans
! * in the meantime), and 2) it's a cheap test.
*/
! if (result)
{
! LWLockAcquire(SInvalLock, LW_SHARED);
! locked = true;
! SHMEM_OFFSET pOffset = stateP[i].procStruct;
!
! if (pOffset != INVALID_OFFSET)
{
! PGPROC *proc = (PGPROC *) MAKE_PTR(pOffset);
!
! /* Fetch xid just once - see GetNewTransactionId */
! TransactionId pxid = proc->xid;
!
! if (!TransactionIdEquals(pxid, xid))
! result = false;
}
}
+ result_known: ;
+
+ if (locked)
+ LWLockRelease(SInvalLock);
+
pfree(xids);
return result;
***************
*** 928,930 ****
--- 1012,1149 ----
return count;
}
+
+ /*
+ * XidCacheAddRunningXid
+ *
+ * Add a TransactionId to the list of known-running transactions.
+ * If there is no space in the cache, mark overflow and return.
+ */
+ void
+ XidCacheAddRunningXid(TransactionId xid)
+ {
+ LWLockAcquire(SInvalLock, LW_SHARED);
+
+ if (MyProc->cache.nxids >= PGPROC_MAX_CACHED_SUBXIDS)
+ {
+ MyProc->cache.overflow = true;
+ LWLockRelease(SInvalLock);
+ return;
+ }
+
+ TransactionIdStore(xid, &(MyProc->cache.xids[MyProc->cache.nxids]));
+ MyProc->cache.nxids ++;
+ LWLockRelease(SInvalLock);
+ }
+
+ #define XidCacheRemove(i) \
+ do { \
+ TransactionIdStore(InvalidTransactionId, &(MyProc->cache.xids[i])); \
+ MyProc->cache.nxids --; \
+ } while (0);
+
+ /*
+ * XidCacheRemoveRunningXids
+ *
+ * Remove a bunch of TransactionIds from the list of known-running
+ * transactions.
+ */
+ void
+ XidCacheRemoveRunningXids(int nxids, TransactionId *xids, TransactionId xid)
+ {
+ int i, j;
+ bool removed = false;
+
+ Assert(!TransactionIdEquals(xid, InvalidTransactionId));
+
+ LWLockAcquire(SInvalLock, LW_SHARED);
+
+ /*
+ * If somebody checks the cache before this is complete,
+ * have them look in pg_subtrans.
+ *
+ * Note that we will reset the overflow flag only if some
+ * Xid was removed. This is OK because if there was no
+ * overflow, then we will find at least one TransactionId,
+ * and if there was overflow, the flag needs to stay unless
+ * we freed some space.
+ */
+ MyProc->cache.overflow = true;
+
+ for (i = 0; i < nxids; i++)
+ {
+ for (j = 0; j < PGPROC_MAX_CACHED_SUBXIDS; j++)
+ {
+ if (TransactionIdEquals(MyProc->cache.xids[j], xids[i]))
+ {
+ XidCacheRemove(j);
+ removed = true;
+ break;
+ }
+ }
+ }
+
+ for (j = 0; j < PGPROC_MAX_CACHED_SUBXIDS; j++)
+ {
+ if (TransactionIdEquals(MyProc->cache.xids[j], xid))
+ {
+ XidCacheRemove(j);
+ removed = true;
+ break;
+ }
+ }
+
+ if (removed)
+ MyProc->cache.overflow = false;
+
+ LWLockRelease(SInvalLock);
+ }
+
+ /*
+ * XidCacheClean
+ *
+ * Fast cache cleanup at transaction end.
+ */
+ void
+ XidCacheClean(void)
+ {
+ LWLockAcquire(SInvalLock, LW_SHARED);
+
+ /*
+ * If somebody checks the cache before this is complete,
+ * have them look in pg_subtrans.
+ */
+ MyProc->cache.overflow = true;
+ MyProc->cache.nxids = 0;
+ MemSet(MyProc->cache.xids, '\0',
+ PGPROC_MAX_CACHED_SUBXIDS * sizeof(TransactionId));
+ MyProc->cache.overflow = false;
+
+ LWLockRelease(SInvalLock);
+ }
+
+ #ifdef XIDCACHE_DEBUG
+ static void
+ DisplayXidCache(int code, Datum arg)
+ {
+ int i;
+ if (MyProc == NULL)
+ {
+ fprintf(stderr,"XidCache: xmin: %d, mainxid: %d, childxid: %d, slow: %d\n",
+ xc_by_recent_xmin,
+ xc_by_main_xid,
+ xc_by_child_xid,
+ xc_slow_answer);
+ }
+ else
+ {
+ fprintf(stderr, "(%s) children:\t",
+ MyProc->cache.overflow ? "overf" : "no overf");
+ for (i = 0; i < PGPROC_MAX_CACHED_SUBXIDS; i++)
+ {
+ fprintf(stderr, "%d ", MyProc->cache.xids[i]);
+ }
+ fprintf(stderr, "\n");
+ }
+ }
+ #endif /* XIDCACHE_DEBUG */
diff -Ncr --exclude-from=diff-ignore 00orig/src/backend/tcop/utility.c 04pgproc/src/backend/tcop/utility.c
*** 00orig/src/backend/tcop/utility.c 2004-07-26 21:59:36.000000000 -0400
--- 04pgproc/src/backend/tcop/utility.c 2004-07-27 10:29:30.000000000 -0400
***************
*** 326,333 ****
{
/*
* START TRANSACTION, as defined by SQL99:
! * Identical to BEGIN, except that it takes a few
! * additional options. Same code for both.
*/
case TRANS_STMT_BEGIN:
case TRANS_STMT_START:
--- 326,332 ----
{
/*
* START TRANSACTION, as defined by SQL99:
! * Identical to BEGIN. Same code for both.
*/
case TRANS_STMT_BEGIN:
case TRANS_STMT_START:
diff -Ncr --exclude-from=diff-ignore 00orig/src/backend/utils/init/miscinit.c 04pgproc/src/backend/utils/init/miscinit.c
*** 00orig/src/backend/utils/init/miscinit.c 2004-07-30 20:55:38.000000000 -0400
--- 04pgproc/src/backend/utils/init/miscinit.c 2004-07-30 20:49:55.000000000 -0400
***************
*** 29,34 ****
--- 29,35 ----
#include
#endif
+ #include "access/xact.h"
#include "catalog/catname.h"
#include "catalog/pg_shadow.h"
#include "libpq/libpq-be.h"
***************
*** 91,96 ****
--- 92,98 ----
static Oid currentlyReindexedHeap = InvalidOid;
static Oid currentlyReindexedIndex = InvalidOid;
+ static TransactionId reindexingTransaction = InvalidTransactionId;
/*
* ReindexIsProcessingHeap
***************
*** 115,131 ****
/*
* SetReindexProcessing
* Set flag that specified heap/index are being reindexed.
- * Pass InvalidOid to indicate that reindexing is not active.
*/
void
SetReindexProcessing(Oid heapOid, Oid indexOid)
{
! /* Args should be both, or neither, InvalidOid */
! Assert((heapOid == InvalidOid) == (indexOid == InvalidOid));
/* Reindexing is not re-entrant. */
! Assert(indexOid == InvalidOid || currentlyReindexedIndex == InvalidOid);
currentlyReindexedHeap = heapOid;
currentlyReindexedIndex = indexOid;
}
/* ----------------------------------------------------------------
--- 117,147 ----
/*
* SetReindexProcessing
* Set flag that specified heap/index are being reindexed.
*/
void
SetReindexProcessing(Oid heapOid, Oid indexOid)
{
! Assert(OidIsValid(heapOid) && OidIsValid(indexOid));
/* Reindexing is not re-entrant. */
! Assert(currentlyReindexedIndex == InvalidOid);
currentlyReindexedHeap = heapOid;
currentlyReindexedIndex = indexOid;
+ reindexingTransaction = GetCurrentTransactionId();
+ }
+
+ /*
+ * ResetReindexProcessing
+ * Unset index/heap reindex status.
+ */
+ void
+ ResetReindexProcessing(bool isAbort)
+ {
+ if (!isAbort || reindexingTransaction == GetCurrentTransactionId())
+ {
+ currentlyReindexedHeap = InvalidOid;
+ currentlyReindexedIndex = InvalidOid;
+ reindexingTransaction = InvalidTransactionId;
+ }
}
/* ----------------------------------------------------------------
diff -Ncr --exclude-from=diff-ignore 00orig/src/include/access/htup.h 04pgproc/src/include/access/htup.h
*** 00orig/src/include/access/htup.h 2004-07-17 18:10:20.000000000 -0400
--- 04pgproc/src/include/access/htup.h 2004-07-27 10:52:44.000000000 -0400
***************
*** 68,101 ****
* object ID (if HEAP_HASOID is set in t_infomask)
* user data fields
*
! * We store five "virtual" fields Xmin, Cmin, Xmax, Cmax, and Xvac
! * in just three physical fields. Xmin is always really stored, but
! * Cmin and Xmax share a field, as do Cmax and Xvac. This works because
! * we know that there are only a limited number of states that a tuple can
! * be in, and that Cmin and Cmax are only interesting for the lifetime of
! * the inserting and deleting transactions respectively. We have the
! * following possible states of a tuple:
! *
! * XMIN CMIN XMAX CMAX XVAC
! *
! * NEW (never deleted, not moved by vacuum):
! * valid valid invalid invalid invalid
! *
! * DELETED BY CREATING XACT:
! * valid valid = XMIN valid invalid
! *
! * DELETED BY OTHER XACT:
! * valid unneeded valid valid invalid
! *
! * MOVED BY VACUUM FULL:
! * valid unneeded maybe-valid unneeded valid
! *
! * This assumes that VACUUM FULL never tries to move a tuple whose Cmin or
! * Cmax is still interesting (ie, insert-in-progress or delete-in-progress).
! *
! * This table shows that if we use an infomask bit to handle the case
! * XMAX=XMIN specially, we never need to store Cmin and Xmax at the same
! * time. Nor do we need to store Cmax and Xvac at the same time.
*
* Following the fixed header fields, the nulls bitmap is stored (beginning
* at t_bits). The bitmap is *not* stored if t_infomask shows that there
--- 68,84 ----
* object ID (if HEAP_HASOID is set in t_infomask)
* user data fields
*
! * We store five "virtual" fields Xmin, Cmin, Xmax, Cmax, and Xvac in four
! * physical fields. Xmin, Cmin and Xmax are always really stored, but
! * Cmax and Xvac share a field. This works because we know that there are
! * only a limited number of states that a tuple can be in, and that Cmax
! * is only interesting for the lifetime of the deleting transactions
! * respectively. This assumes that VACUUM FULL never tries to move a
! * tuple whose Cmax is still interesting (ie, delete-in-progress).
! *
! * Note that in 7.3 and 7.4 a similar idea was applied to Xmax and Cmin.
! * However, with the advent of subtransactions, a tuple may need both Xmax
! * and Cmin simultaneously, so this is no longer possible.
*
* Following the fixed header fields, the nulls bitmap is stored (beginning
* at t_bits). The bitmap is *not* stored if t_infomask shows that there
***************
*** 424,430 ****
#define XLOG_HEAP_MOVE 0x30
#define XLOG_HEAP_CLEAN 0x40
#define XLOG_HEAP_NEWPAGE 0x50
! /* opcodes 0x60, 0x70 still free */
#define XLOG_HEAP_OPMASK 0x70
/*
* When we insert 1st item on new page in INSERT/UPDATE
--- 407,413 ----
#define XLOG_HEAP_MOVE 0x30
#define XLOG_HEAP_CLEAN 0x40
#define XLOG_HEAP_NEWPAGE 0x50
! /* opcode 0x60 still free */
#define XLOG_HEAP_OPMASK 0x70
/*
* When we insert 1st item on new page in INSERT/UPDATE
diff -Ncr --exclude-from=diff-ignore 00orig/src/include/access/xact.h 04pgproc/src/include/access/xact.h
*** 00orig/src/include/access/xact.h 2004-07-31 11:22:35.000000000 -0400
--- 04pgproc/src/include/access/xact.h 2004-07-31 18:25:19.450972484 -0400
***************
*** 42,50 ****
extern bool XactReadOnly;
/*
! * end-of-transaction cleanup callbacks for dynamically loaded modules
*/
! typedef void (*EOXactCallback) (bool isCommit, void *arg);
/* ----------------
--- 42,59 ----
extern bool XactReadOnly;
/*
! * start- and end-of-transaction callbacks for dynamically loaded modules
*/
! typedef enum
! {
! XACT_EVENT_ABORT,
! XACT_EVENT_COMMIT,
! XACT_EVENT_START_SUB,
! XACT_EVENT_ABORT_SUB,
! XACT_EVENT_COMMIT_SUB
! } XactEvent;
!
! typedef void (*XactCallback) (XactEvent event, TransactionId parentXid, void *arg);
/* ----------------
***************
*** 118,125 ****
extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
extern void RequireTransactionChain(void *stmtNode, const char *stmtType);
extern bool IsInTransactionChain(void *stmtNode);
! extern void RegisterEOXactCallback(EOXactCallback callback, void *arg);
! extern void UnregisterEOXactCallback(EOXactCallback callback, void *arg);
extern void RecordTransactionCommit(void);
--- 127,134 ----
extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
extern void RequireTransactionChain(void *stmtNode, const char *stmtType);
extern bool IsInTransactionChain(void *stmtNode);
! extern void RegisterXactCallback(XactCallback callback, void *arg);
! extern void UnregisterXactCallback(XactCallback callback, void *arg);
extern void RecordTransactionCommit(void);
diff -Ncr --exclude-from=diff-ignore 00orig/src/include/miscadmin.h 04pgproc/src/include/miscadmin.h
*** 00orig/src/include/miscadmin.h 2004-07-30 20:55:38.000000000 -0400
--- 04pgproc/src/include/miscadmin.h 2004-07-30 20:48:52.000000000 -0400
***************
*** 308,313 ****
--- 308,314 ----
extern void IgnoreSystemIndexes(bool mode);
extern bool IsIgnoringSystemIndexes(void);
extern void SetReindexProcessing(Oid heapOid, Oid indexOid);
+ extern void ResetReindexProcessing(bool isAbort);
extern bool ReindexIsProcessingHeap(Oid heapOid);
extern bool ReindexIsProcessingIndex(Oid indexOid);
extern void CreateDataDirLockFile(const char *datadir, bool amPostmaster);
diff -Ncr --exclude-from=diff-ignore 00orig/src/include/nodes/pg_list.h 04pgproc/src/include/nodes/pg_list.h
*** 00orig/src/include/nodes/pg_list.h 2004-06-03 15:59:01.000000000 -0400
--- 04pgproc/src/include/nodes/pg_list.h 2004-07-30 23:47:44.000000000 -0400
***************
*** 232,237 ****
--- 232,245 ----
extern List *list_copy_tail(List *list, int nskip);
/*
+ * We do not need full separate support for List of TransactionIds. It
+ * seems a good idea, however, to keep their use separate from integer
+ * list, just in case we want to change Xid's representation in the future.
+ */
+ #define lfirst_xid(lc) lfirst_int(lc)
+ #define lappend_xid(list, datum) lappend_int(list, datum)
+
+ /*
* To ease migration to the new list API, a set of compatibility
* macros are provided that reduce the impact of the list API changes
* as far as possible. Until client code has been rewritten to use the
diff -Ncr --exclude-from=diff-ignore 00orig/src/include/storage/proc.h 04pgproc/src/include/storage/proc.h
*** 00orig/src/include/storage/proc.h 2004-07-26 21:59:44.000000000 -0400
--- 04pgproc/src/include/storage/proc.h 2004-07-28 14:41:03.000000000 -0400
***************
*** 19,24 ****
--- 19,44 ----
#include "storage/lock.h"
#include "storage/pg_sema.h"
+ /*
+ * XXX This number is made up ...
+ */
+ #define PGPROC_MAX_CACHED_SUBXIDS 20
+
+ /*
+ * Each backend keeps track of (some of) its subtransactions'
+ * TransactionIds in the PGPROC struct.
+ *
+ * We also keep track of whether the cache overflowed. If it
+ * hasn't overflowed, we can assume that a Xid that's not present
+ * in the cache is not a running transaction. Else we have to look
+ * at pg_subtrans.
+ */
+ struct XidCache {
+ /* running Xids cache */
+ int nxids;
+ TransactionId xids[PGPROC_MAX_CACHED_SUBXIDS];
+ bool overflow;
+ };
/*
* Each backend has a PGPROC struct in shared memory. There is also a list of
***************
*** 39,44 ****
--- 59,66 ----
TransactionId xid; /* transaction currently being executed by
* this proc */
+ struct XidCache cache; /* Xid cache */
+
TransactionId xmin; /* minimal running XID as it was when we
* were starting our xact: vacuum must not
* remove tuples deleted by xid >= xmin ! */
diff -Ncr --exclude-from=diff-ignore 00orig/src/include/storage/sinval.h 04pgproc/src/include/storage/sinval.h
*** 00orig/src/include/storage/sinval.h 2004-06-03 15:59:03.000000000 -0400
--- 04pgproc/src/include/storage/sinval.h 2004-07-27 23:30:29.000000000 -0400
***************
*** 115,118 ****
--- 115,123 ----
extern void EnableCatchupInterrupt(void);
extern bool DisableCatchupInterrupt(void);
+ /* Xid cache updaters */
+ extern void XidCacheAddRunningXid(TransactionId xid);
+ extern void XidCacheRemoveRunningXids(int nxids, TransactionId *xids, TransactionId xid);
+ extern void XidCacheClean(void);
+
#endif /* SINVAL_H */
diff -Ncr --exclude-from=diff-ignore 00orig/src/pl/plpgsql/src/pl_exec.c 04pgproc/src/pl/plpgsql/src/pl_exec.c
*** 00orig/src/pl/plpgsql/src/pl_exec.c 2004-07-31 17:47:54.453946649 -0400
--- 04pgproc/src/pl/plpgsql/src/pl_exec.c 2004-07-31 18:35:32.448511179 -0400
***************
*** 4198,4223 ****
* structs that are using it as no longer active.
*/
void
! plpgsql_eoxact(bool isCommit, void *arg)
{
PLpgSQL_expr *expr;
PLpgSQL_expr *enext;
! /* Mark all active exprs as inactive */
! for (expr = active_simple_exprs; expr; expr = enext)
{
! enext = expr->expr_simple_next;
! expr->expr_simple_state = NULL;
! expr->expr_simple_next = NULL;
}
- active_simple_exprs = NULL;
- /*
- * If we are doing a clean transaction shutdown, free the EState
- * (so that any remaining resources will be released correctly).
- * In an abort, we expect the regular abort recovery procedures to
- * release everything of interest.
- */
- if (isCommit && simple_eval_estate)
- FreeExecutorState(simple_eval_estate);
- simple_eval_estate = NULL;
}
--- 4198,4238 ----
* structs that are using it as no longer active.
*/
void
! plpgsql_xact_cb(XactEvent event, TransactionId parentXid, void *arg)
{
PLpgSQL_expr *expr;
PLpgSQL_expr *enext;
! switch (event)
{
! /*
! * Nothing to do at subtransaction events
! * XXX -- really? Maybe we need to clean simple_eval_estate on abort?
! */
! case XACT_EVENT_START_SUB:
! case XACT_EVENT_ABORT_SUB:
! case XACT_EVENT_COMMIT_SUB:
! return;
!
! case XACT_EVENT_ABORT:
! case XACT_EVENT_COMMIT:
! /* Mark all active exprs as inactive */
! for (expr = active_simple_exprs; expr; expr = enext)
! {
! enext = expr->expr_simple_next;
! expr->expr_simple_state = NULL;
! expr->expr_simple_next = NULL;
! }
! active_simple_exprs = NULL;
! /*
! * If we are doing a clean transaction shutdown, free the EState
! * (so that any remaining resources will be released correctly).
! * In an abort, we expect the regular abort recovery procedures to
! * release everything of interest.
! */
! if (event == XACT_EVENT_COMMIT && simple_eval_estate)
! FreeExecutorState(simple_eval_estate);
! simple_eval_estate = NULL;
! break;
}
}
diff -Ncr --exclude-from=diff-ignore 00orig/src/pl/plpgsql/src/pl_handler.c 04pgproc/src/pl/plpgsql/src/pl_handler.c
*** 00orig/src/pl/plpgsql/src/pl_handler.c 2004-07-31 17:47:54.463945207 -0400
--- 04pgproc/src/pl/plpgsql/src/pl_handler.c 2004-07-31 18:53:34.192405370 -0400
***************
*** 66,72 ****
plpgsql_HashTableInit();
! RegisterEOXactCallback(plpgsql_eoxact, NULL);
plpgsql_firstcall = 0;
}
--- 66,72 ----
plpgsql_HashTableInit();
! RegisterXactCallback(plpgsql_xact_cb, NULL);
plpgsql_firstcall = 0;
}
diff -Ncr --exclude-from=diff-ignore 00orig/src/pl/plpgsql/src/plpgsql.h 04pgproc/src/pl/plpgsql/src/plpgsql.h
*** 00orig/src/pl/plpgsql/src/plpgsql.h 2004-07-31 11:22:40.000000000 -0400
--- 04pgproc/src/pl/plpgsql/src/plpgsql.h 2004-07-31 18:33:30.217150313 -0400
***************
*** 696,702 ****
FunctionCallInfo fcinfo);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function * func,
TriggerData *trigdata);
! extern void plpgsql_eoxact(bool isCommit, void *arg);
/* ----------
* Functions for the dynamic string handling in pl_funcs.c
--- 696,702 ----
FunctionCallInfo fcinfo);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function * func,
TriggerData *trigdata);
! extern void plpgsql_xact_cb(XactEvent event, TransactionId parentXid, void *arg);
/* ----------
* Functions for the dynamic string handling in pl_funcs.c