diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/access/transam/xact.c 03pgproc/src/backend/access/transam/xact.c *** 02subbegin/src/backend/access/transam/xact.c 2004-07-26 12:39:14.000000000 -0400 --- 03pgproc/src/backend/access/transam/xact.c 2004-07-26 15:36:52.973431648 -0400 *************** *** 1020,1025 **** --- 1020,1028 ---- TransactionIdAbortTree(nchildren, children); TransactionIdAbort(xid); + XidCacheRemoveRunningXids(nchildren, children); + XidCacheAddNotRunningXid(xid); + END_CRIT_SECTION(); } *************** *** 1159,1164 **** --- 1162,1170 ---- TransactionIdAbortTree(nchildren, children); TransactionIdAbort(xid); + XidCacheRemoveRunningXids(nchildren, children); + XidCacheAddNotRunningXid(xid); + END_CRIT_SECTION(); } *************** *** 1244,1250 **** * check the current transaction state */ if (s->state != TRANS_DEFAULT) ! elog(WARNING, "StartTransaction and not in default state"); /* * set the current transaction state information appropriately during --- 1250,1257 ---- * check the current transaction state */ if (s->state != TRANS_DEFAULT) ! elog(WARNING, "StartTransaction while in %s state", ! TransStateAsString(s->state)); /* * set the current transaction state information appropriately during *************** *** 1321,1327 **** * check the current transaction state */ if (s->state != TRANS_INPROGRESS) ! elog(WARNING, "CommitTransaction and not in in-progress state"); Assert(s->parent == NULL); /* --- 1328,1335 ---- * check the current transaction state */ if (s->state != TRANS_INPROGRESS) ! elog(WARNING, "CommitTransaction while in %s state", ! TransStateAsString(s->state)); Assert(s->parent == NULL); /* *************** *** 1355,1361 **** /* handle commit for large objects [ PA, 7/17/98 ] */ /* XXX probably this does not belong here */ ! lo_commit(true); /* NOTIFY commit must come before lower-level cleanup */ AtCommit_Notify(); --- 1363,1369 ---- /* handle commit for large objects [ PA, 7/17/98 ] */ /* XXX probably this does not belong here */ ! AtEOXact_LargeObject(true); /* NOTIFY commit must come before lower-level cleanup */ AtCommit_Notify(); *************** *** 1381,1391 **** --- 1389,1410 ---- */ if (MyProc != NULL) { + TransactionId *children; + int nchildren; + /* Lock SInvalLock because that's what GetSnapshotData uses. */ LWLockAcquire(SInvalLock, LW_EXCLUSIVE); MyProc->xid = InvalidTransactionId; MyProc->xmin = InvalidTransactionId; LWLockRelease(SInvalLock); + + /* + * Clean up the Xid cache. This has to happen _after_ we take our + * main Xid out of PGPROC! + */ + nchildren = xactGetCommittedChildren(&children); + XidCacheRemoveRunningXids(nchildren, children); + XidCacheAddNotRunningXid(s->transactionIdData); } /* *************** *** 1488,1494 **** * check the current transaction state */ if (s->state != TRANS_INPROGRESS) ! elog(WARNING, "AbortTransaction and not in in-progress state"); Assert(s->parent == NULL); /* --- 1507,1514 ---- * check the current transaction state */ if (s->state != TRANS_INPROGRESS) ! elog(WARNING, "AbortTransaction while in %s state", ! TransStateAsString(s->state)); Assert(s->parent == NULL); /* *************** *** 1517,1523 **** */ DeferredTriggerAbortXact(); AtAbort_Portals(); ! lo_commit(false); /* 'false' means it's abort */ AtAbort_Notify(); AtEOXact_UpdatePasswordFile(false); --- 1537,1543 ---- */ DeferredTriggerAbortXact(); AtAbort_Portals(); ! AtEOXact_LargeObject(false); /* 'false' means it's abort */ AtAbort_Notify(); AtEOXact_UpdatePasswordFile(false); *************** *** 1805,1810 **** --- 1825,1833 ---- */ case TBLOCK_SUBENDABORT_RELEASE: abortSubxacts(false); + s = CurrentTransactionState; + AssertState(s->blockState == TBLOCK_SUBINPROGRESS || + s->blockState == TBLOCK_INPROGRESS); break; /* *************** *** 1816,1823 **** { char *name = abortSubxacts(true); Assert(PointerIsValid(name)); ! DefineSavepoint(name); /* changed by DefineSavepoint */ s = CurrentTransactionState; AssertState(s->blockState == TBLOCK_SUBBEGIN); /* --- 1839,1849 ---- { char *name = abortSubxacts(true); + s = CurrentTransactionState; Assert(PointerIsValid(name)); ! AssertState(s->blockState == TBLOCK_SUBINPROGRESS || ! s->blockState == TBLOCK_INPROGRESS); ! DefineSavepoint(name); s = CurrentTransactionState; AssertState(s->blockState == TBLOCK_SUBBEGIN); /* *************** *** 2868,2874 **** TransactionState s = CurrentTransactionState; if (s->state != TRANS_DEFAULT) ! elog(WARNING, "StartSubTransaction and not in default state"); s->state = TRANS_START; --- 2894,2901 ---- TransactionState s = CurrentTransactionState; if (s->state != TRANS_DEFAULT) ! elog(WARNING, "StartSubTransaction while in %s state", ! TransStateAsString(s->state)); s->state = TRANS_START; *************** *** 2888,2893 **** --- 2915,2927 ---- 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(); *************** *** 2915,2921 **** ShowTransactionState("CommitSubTransaction"); if (s->state != TRANS_INPROGRESS) ! elog(WARNING, "CommitSubTransaction and not in in-progress state"); /* Pre-commit processing */ AtSubCommit_Portals(s->parent->transactionIdData, --- 2949,2956 ---- ShowTransactionState("CommitSubTransaction"); if (s->state != TRANS_INPROGRESS) ! elog(WARNING, "CommitSubTransaction while in %s state", ! TransStateAsString(s->state)); /* Pre-commit processing */ AtSubCommit_Portals(s->parent->transactionIdData, *************** *** 2950,2955 **** --- 2985,2995 ---- RESOURCE_RELEASE_AFTER_LOCKS, true, false); + AtSubCommit_LargeObject(s->parent->transactionIdData); + AtEOSubXact_UpdatePasswordFile(true, s->parent->transactionIdData); + AtEOSubXact_Files(true, s->transactionIdData, s->parent->transactionIdData); + AtEOSubXact_Namespace(true, s->parent->transactionIdData); + AtSubCommit_Notify(); AtEOXact_GUC(true, true); AtEOSubXact_on_commit_actions(true, s->transactionIdData, *************** *** 2975,2981 **** ShowTransactionState("AbortSubTransaction"); if (s->state != TRANS_INPROGRESS) ! elog(WARNING, "AbortSubTransaction and not in in-progress state"); HOLD_INTERRUPTS(); --- 3015,3022 ---- ShowTransactionState("AbortSubTransaction"); if (s->state != TRANS_INPROGRESS) ! elog(WARNING, "AbortSubTransaction while in %s state", ! TransStateAsString(s->state)); HOLD_INTERRUPTS(); *************** *** 3004,3009 **** --- 3045,3051 ---- */ RecordSubTransactionAbort(); + AtSubAbort_LargeObject(); /* Post-abort cleanup */ AtSubAbort_smgr(); *************** *** 3028,3033 **** --- 3070,3079 ---- AtEOXact_GUC(false, true); AtEOSubXact_on_commit_actions(false, s->transactionIdData, s->parent->transactionIdData); + AtEOSubXact_UpdatePasswordFile(false, s->parent->transactionIdData); + AtEOSubXact_Files(false, s->transactionIdData, s->parent->transactionIdData); + AtEOSubXact_Namespace(false, s->parent->transactionIdData); + /* * Reset user id which might have been changed transiently. Here we *************** *** 3059,3065 **** ShowTransactionState("CleanupSubTransaction"); if (s->state != TRANS_ABORT) ! elog(WARNING, "CleanupSubTransaction and not in aborted state"); AtSubCleanup_Portals(); --- 3105,3112 ---- ShowTransactionState("CleanupSubTransaction"); if (s->state != TRANS_ABORT) ! elog(WARNING, "CleanupSubTransaction while in %s state", ! TransStateAsString(s->state)); AtSubCleanup_Portals(); *************** *** 3090,3096 **** TransactionState s = CurrentTransactionState; if (s->state != TRANS_DEFAULT) ! elog(WARNING, "StartAbortedSubTransaction and not in default state"); s->state = TRANS_START; --- 3137,3144 ---- TransactionState s = CurrentTransactionState; if (s->state != TRANS_DEFAULT) ! elog(WARNING, "StartAbortedSubTransaction while in %s state", ! TransStateAsString(s->state)); s->state = TRANS_START; *************** *** 3169,3175 **** TransactionState s = CurrentTransactionState; if (s->state != TRANS_DEFAULT) ! elog(WARNING, "PopTransaction and not in default state"); if (s->parent == NULL) elog(FATAL, "PopTransaction with no parent"); --- 3217,3224 ---- TransactionState s = CurrentTransactionState; if (s->state != TRANS_DEFAULT) ! elog(WARNING, "PopTransaction while in %s state", ! TransStateAsString(s->state)); if (s->parent == NULL) elog(FATAL, "PopTransaction with no parent"); diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/catalog/namespace.c 03pgproc/src/backend/catalog/namespace.c *** 02subbegin/src/backend/catalog/namespace.c 2004-06-23 15:21:12.000000000 -0400 --- 03pgproc/src/backend/catalog/namespace.c 2004-07-25 19:31:44.000000000 -0400 *************** *** 108,118 **** * in a particular backend session (this happens when a CREATE TEMP TABLE * command is first executed). Thereafter it's the OID of the temp namespace. * firstTempTransaction flags whether we've committed creation of the TEMP ! * namespace or not. */ static Oid myTempNamespace = InvalidOid; ! static bool firstTempTransaction = false; /* * "Special" namespace for CREATE SCHEMA. If set, it's the first search --- 108,120 ---- * in a particular backend session (this happens when a CREATE TEMP TABLE * command is first executed). Thereafter it's the OID of the temp namespace. * firstTempTransaction flags whether we've committed creation of the TEMP ! * namespace or not; the TransactionId propagates up the transaction tree, ! * so the main transaction will correctly recognize the flag if all ! * intermediate subtransactions commit. */ static Oid myTempNamespace = InvalidOid; ! static TransactionId firstTempTransaction = InvalidTransactionId; /* * "Special" namespace for CREATE SCHEMA. If set, it's the first search *************** *** 1688,1694 **** */ myTempNamespace = namespaceId; ! firstTempTransaction = true; namespaceSearchPathValid = false; /* need to rebuild list */ } --- 1690,1698 ---- */ myTempNamespace = namespaceId; ! /* It should not be done already. */ ! AssertState(firstTempTransaction == InvalidTransactionId); ! firstTempTransaction = GetCurrentTransactionId(); namespaceSearchPathValid = false; /* need to rebuild list */ } *************** *** 1707,1713 **** * temp tables at backend shutdown. (We only want to register the * callback once per session, so this is a good place to do it.) */ ! if (firstTempTransaction) { if (isCommit) on_shmem_exit(RemoveTempRelationsCallback, 0); --- 1711,1717 ---- * temp tables at backend shutdown. (We only want to register the * callback once per session, so this is a good place to do it.) */ ! if (firstTempTransaction == GetCurrentTransactionId()) { if (isCommit) on_shmem_exit(RemoveTempRelationsCallback, 0); *************** *** 1716,1722 **** myTempNamespace = InvalidOid; namespaceSearchPathValid = false; /* need to rebuild list */ } ! firstTempTransaction = false; } /* --- 1720,1726 ---- myTempNamespace = InvalidOid; namespaceSearchPathValid = false; /* need to rebuild list */ } ! firstTempTransaction = InvalidTransactionId; } /* *************** *** 1730,1735 **** --- 1734,1759 ---- } /* + * AtEOSubXact_Namespace + * + * At subtransaction commit, propagate the temp-namespace-creation + * flag to the parent transaction. + * + * At subtransaction abort, forget the flag if we set it up. + */ + void + AtEOSubXact_Namespace(bool isCommit, TransactionId parentXid) + { + if (firstTempTransaction == GetCurrentTransactionId()) + { + if (isCommit) + firstTempTransaction = parentXid; + else + firstTempTransaction = InvalidTransactionId; + } + } + + /* * Remove all relations in the specified temp namespace. * * This is called at backend shutdown (if we made any temp relations). diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/commands/user.c 03pgproc/src/backend/commands/user.c *** 02subbegin/src/backend/commands/user.c 2004-05-26 10:27:07.000000000 -0400 --- 03pgproc/src/backend/commands/user.c 2004-07-15 15:41:21.000000000 -0400 *************** *** 44,51 **** extern bool Password_encryption; ! static bool user_file_update_needed = false; ! static bool group_file_update_needed = false; static void CheckPgUserAclNotNull(void); --- 44,61 ---- extern bool Password_encryption; ! /* ! * The need-to-update-files flags are a pair of TransactionId that show what ! * level of the transaction tree requested the update. To register an update, ! * the transaction saves its own TransactionId in the flag, unless the value ! * was already set to a valid TransactionId. If it aborts and the value is its ! * TransactionId, it resets the value to InvalidTransactionId. If it commits, ! * it changes the value to its parent's TransactionId. This way the value is ! * propagated up to the topmost transaction, which will update the files if a ! * valid TransactionId is detected. ! */ ! static TransactionId user_file_update_needed = InvalidTransactionId; ! static TransactionId group_file_update_needed = InvalidTransactionId; static void CheckPgUserAclNotNull(void); *************** *** 402,409 **** Datum update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS) { ! user_file_update_needed = true; ! group_file_update_needed = true; return PointerGetDatum(NULL); } --- 412,422 ---- Datum update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS) { ! if (user_file_update_needed == InvalidTransactionId) ! user_file_update_needed = GetCurrentTransactionId(); ! ! if (group_file_update_needed == InvalidTransactionId) ! group_file_update_needed = GetCurrentTransactionId(); return PointerGetDatum(NULL); } *************** *** 429,441 **** Relation urel = NULL; Relation grel = NULL; ! if (!(user_file_update_needed || group_file_update_needed)) return; if (!isCommit) { ! user_file_update_needed = false; ! group_file_update_needed = false; return; } --- 442,455 ---- Relation urel = NULL; Relation grel = NULL; ! if (user_file_update_needed == InvalidTransactionId && ! group_file_update_needed == InvalidTransactionId) return; if (!isCommit) { ! user_file_update_needed = InvalidTransactionId; ! group_file_update_needed = InvalidTransactionId; return; } *************** *** 447,468 **** * pg_shadow or pg_group, which likely won't have gotten a strong * enough lock), so get the locks we need before writing anything. */ ! if (user_file_update_needed) urel = heap_openr(ShadowRelationName, ExclusiveLock); ! if (group_file_update_needed) grel = heap_openr(GroupRelationName, ExclusiveLock); /* Okay to write the files */ ! if (user_file_update_needed) { ! user_file_update_needed = false; write_user_file(urel); heap_close(urel, NoLock); } ! if (group_file_update_needed) { ! group_file_update_needed = false; write_group_file(grel); heap_close(grel, NoLock); } --- 461,482 ---- * pg_shadow or pg_group, which likely won't have gotten a strong * enough lock), so get the locks we need before writing anything. */ ! if (user_file_update_needed != InvalidTransactionId) urel = heap_openr(ShadowRelationName, ExclusiveLock); ! if (group_file_update_needed != InvalidTransactionId) grel = heap_openr(GroupRelationName, ExclusiveLock); /* Okay to write the files */ ! if (user_file_update_needed != InvalidTransactionId) { ! user_file_update_needed = InvalidTransactionId; write_user_file(urel); heap_close(urel, NoLock); } ! if (group_file_update_needed != InvalidTransactionId) { ! group_file_update_needed = InvalidTransactionId; write_group_file(grel); heap_close(grel, NoLock); } *************** *** 473,479 **** --- 487,520 ---- SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE); } + /* + * AtEOSubXact_UpdatePasswordFile + * + * Called at subtransaction end, this routine resets or updates the + * need-to-update-files flags. + */ + void + AtEOSubXact_UpdatePasswordFile(bool isCommit, TransactionId parentXid) + { + TransactionId myXid = GetCurrentTransactionId(); + + if (isCommit) + { + if (user_file_update_needed == myXid) + user_file_update_needed = parentXid; + + if (group_file_update_needed == myXid) + group_file_update_needed = parentXid; + } + else + { + if (user_file_update_needed == myXid) + user_file_update_needed = InvalidTransactionId; + if (group_file_update_needed == myXid) + group_file_update_needed = InvalidTransactionId; + } + } /* * CREATE USER *************** *** 728,734 **** /* * Set flag to update flat password file at commit. */ ! user_file_update_needed = true; } --- 769,776 ---- /* * Set flag to update flat password file at commit. */ ! if (user_file_update_needed == InvalidTransactionId) ! user_file_update_needed = GetCurrentTransactionId(); } *************** *** 925,931 **** /* * Set flag to update flat password file at commit. */ ! user_file_update_needed = true; } --- 967,974 ---- /* * Set flag to update flat password file at commit. */ ! if (user_file_update_needed == InvalidTransactionId) ! user_file_update_needed = GetCurrentTransactionId(); } *************** *** 1147,1153 **** /* * Set flag to update flat password file at commit. */ ! user_file_update_needed = true; } --- 1190,1197 ---- /* * Set flag to update flat password file at commit. */ ! if (user_file_update_needed == InvalidTransactionId) ! user_file_update_needed = GetCurrentTransactionId(); } *************** *** 1233,1239 **** ReleaseSysCache(oldtuple); heap_close(rel, NoLock); ! user_file_update_needed = true; } --- 1277,1284 ---- ReleaseSysCache(oldtuple); heap_close(rel, NoLock); ! if (user_file_update_needed == InvalidTransactionId) ! user_file_update_needed = GetCurrentTransactionId(); } *************** *** 1438,1444 **** /* * Set flag to update flat group file at commit. */ ! group_file_update_needed = true; } --- 1483,1490 ---- /* * Set flag to update flat group file at commit. */ ! if (group_file_update_needed == InvalidTransactionId) ! group_file_update_needed = GetCurrentTransactionId(); } *************** *** 1590,1596 **** /* * Set flag to update flat group file at commit. */ ! group_file_update_needed = true; } /* --- 1636,1643 ---- /* * Set flag to update flat group file at commit. */ ! if (group_file_update_needed == InvalidTransactionId) ! group_file_update_needed = GetCurrentTransactionId(); } /* *************** *** 1730,1736 **** /* * Set flag to update flat group file at commit. */ ! group_file_update_needed = true; } --- 1777,1784 ---- /* * Set flag to update flat group file at commit. */ ! if (group_file_update_needed == InvalidTransactionId) ! group_file_update_needed = GetCurrentTransactionId(); } *************** *** 1776,1780 **** heap_close(rel, NoLock); heap_freetuple(tup); ! group_file_update_needed = true; } --- 1824,1829 ---- heap_close(rel, NoLock); heap_freetuple(tup); ! if (group_file_update_needed == InvalidTransactionId) ! group_file_update_needed = GetCurrentTransactionId(); } diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/libpq/be-fsstubs.c 03pgproc/src/backend/libpq/be-fsstubs.c *** 02subbegin/src/backend/libpq/be-fsstubs.c 2004-07-16 01:40:09.000000000 -0400 --- 03pgproc/src/backend/libpq/be-fsstubs.c 2004-07-26 15:39:35.305753384 -0400 *************** *** 18,29 **** * * These functions operate in a private MemoryContext, which means * that large object descriptors hang around until we destroy the context. ! * That happens in lo_commit(). It'd be possible to prolong the lifetime ! * of the context so that LO FDs are good across transactions (for example, ! * we could release the context only if we see that no FDs remain open). ! * But we'd need additional state in order to do the right thing at the ! * end of an aborted transaction. FDs opened during an aborted xact would ! * still need to be closed, since they might not be pointing at valid * relations at all. Locking semantics are also an interesting problem * if LOs stay open across transactions. For now, we'll stick with the * existing documented semantics of LO FDs: they're only good within a --- 18,29 ---- * * These functions operate in a private MemoryContext, which means * that large object descriptors hang around until we destroy the context. ! * That happens in AtEOXact_LargeObject(). It'd be possible to prolong the ! * lifetime of the context so that LO FDs are good across transactions (for ! * example, we could release the context only if we see that no FDs remain ! * open). But we'd need additional state in order to do the right thing at ! * the end of an aborted transaction. FDs opened during an aborted xact ! * would still need to be closed, since they might not be pointing at valid * relations at all. Locking semantics are also an interesting problem * if LOs stay open across transactions. For now, we'll stick with the * existing documented semantics of LO FDs: they're only good within a *************** *** 38,49 **** --- 38,51 ---- #include #include + #include "access/xact.h" #include "libpq/be-fsstubs.h" #include "libpq/libpq-fs.h" #include "miscadmin.h" #include "storage/fd.h" #include "storage/large_object.h" #include "utils/memutils.h" + #include "utils/resowner.h" /* [PA] is Pascal André */ *************** *** 80,85 **** --- 82,88 ---- LargeObjectDesc *lobjDesc; int fd; MemoryContext currentContext; + ResourceOwner currentOwner; #if FSDB elog(DEBUG4, "lo_open(%u,%d)", lobjId, mode); *************** *** 93,98 **** --- 96,103 ---- ALLOCSET_DEFAULT_MAXSIZE); currentContext = MemoryContextSwitchTo(fscxt); + currentOwner = CurrentResourceOwner; + CurrentResourceOwner = TopTransactionResourceOwner; lobjDesc = inv_open(lobjId, mode); *************** *** 108,113 **** --- 113,119 ---- fd = newLOfd(lobjDesc); MemoryContextSwitchTo(currentContext); + CurrentResourceOwner = currentOwner; PG_RETURN_INT32(fd); } *************** *** 117,122 **** --- 123,129 ---- { int32 fd = PG_GETARG_INT32(0); MemoryContext currentContext; + ResourceOwner currentOwner; if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) { *************** *** 131,142 **** --- 138,152 ---- Assert(fscxt != NULL); currentContext = MemoryContextSwitchTo(fscxt); + currentOwner = CurrentResourceOwner; + CurrentResourceOwner = TopTransactionResourceOwner; inv_close(cookies[fd]); deleteLOfd(fd); MemoryContextSwitchTo(currentContext); + CurrentResourceOwner = currentOwner; PG_RETURN_INT32(0); } *************** *** 232,237 **** --- 242,248 ---- int32 mode = PG_GETARG_INT32(0); LargeObjectDesc *lobjDesc; MemoryContext currentContext; + ResourceOwner currentOwner; Oid lobjId; if (fscxt == NULL) *************** *** 242,247 **** --- 253,260 ---- ALLOCSET_DEFAULT_MAXSIZE); currentContext = MemoryContextSwitchTo(fscxt); + currentOwner = CurrentResourceOwner; + CurrentResourceOwner = TopTransactionResourceOwner; lobjDesc = inv_create(mode); *************** *** 256,261 **** --- 269,275 ---- inv_close(lobjDesc); MemoryContextSwitchTo(currentContext); + CurrentResourceOwner = currentOwner; PG_RETURN_OID(lobjId); } *************** *** 292,300 **** --- 306,317 ---- if (fscxt != NULL) { MemoryContext currentContext; + ResourceOwner currentOwner; int i; currentContext = MemoryContextSwitchTo(fscxt); + currentOwner = CurrentResourceOwner; + CurrentResourceOwner = TopTransactionResourceOwner; for (i = 0; i < cookies_size; i++) { if (cookies[i] != NULL && cookies[i]->id == lobjId) *************** *** 304,309 **** --- 321,327 ---- } } MemoryContextSwitchTo(currentContext); + CurrentResourceOwner = currentOwner; } /* *************** *** 368,373 **** --- 386,393 ---- char fnamebuf[MAXPGPATH]; LargeObjectDesc *lobj; Oid lobjOid; + MemoryContext currentContext; + ResourceOwner currentOwner; #ifndef ALLOW_DANGEROUS_LO_FUNCTIONS if (!superuser()) *************** *** 377,382 **** --- 397,406 ---- errhint("Anyone can use the client-side lo_import() provided by libpq."))); #endif + currentContext = MemoryContextSwitchTo(fscxt); + currentOwner = CurrentResourceOwner; + CurrentResourceOwner = TopTransactionResourceOwner; + /* * open the file to be read in */ *************** *** 399,405 **** lobjOid = lobj->id; /* ! * read in from the Unix file and write to the inversion file */ while ((nbytes = FileRead(fd, buf, BUFSIZE)) > 0) { --- 423,429 ---- lobjOid = lobj->id; /* ! * read in from the filesystem and write to the inversion file */ while ((nbytes = FileRead(fd, buf, BUFSIZE)) > 0) { *************** *** 416,421 **** --- 440,447 ---- FileClose(fd); inv_close(lobj); + MemoryContextSwitchTo(currentContext); + CurrentResourceOwner = currentOwner; PG_RETURN_OID(lobjOid); } *************** *** 435,440 **** --- 461,468 ---- char fnamebuf[MAXPGPATH]; LargeObjectDesc *lobj; mode_t oumask; + MemoryContext currentContext; + ResourceOwner currentOwner; #ifndef ALLOW_DANGEROUS_LO_FUNCTIONS if (!superuser()) *************** *** 444,449 **** --- 472,481 ---- errhint("Anyone can use the client-side lo_export() provided by libpq."))); #endif + currentContext = MemoryContextSwitchTo(fscxt); + currentOwner = CurrentResourceOwner; + CurrentResourceOwner = TopTransactionResourceOwner; + /* * open the inversion object (no need to test for failure) */ *************** *** 486,508 **** FileClose(fd); inv_close(lobj); PG_RETURN_INT32(1); } /* ! * lo_commit - * prepares large objects for transaction commit [PA, 7/17/98] */ void ! lo_commit(bool isCommit) { int i; ! MemoryContext currentContext; if (fscxt == NULL) return; /* no LO operations in this xact */ ! currentContext = MemoryContextSwitchTo(fscxt); /* * Clean out still-open index scans (not necessary if aborting) and --- 518,544 ---- FileClose(fd); inv_close(lobj); + MemoryContextSwitchTo(currentContext); + CurrentResourceOwner = currentOwner; + PG_RETURN_INT32(1); } /* ! * AtEOXact_LargeObject - * prepares large objects for transaction commit [PA, 7/17/98] */ void ! AtEOXact_LargeObject(bool isCommit) { int i; ! ResourceOwner currentOwner; if (fscxt == NULL) return; /* no LO operations in this xact */ ! currentOwner = CurrentResourceOwner; ! CurrentResourceOwner = TopTransactionResourceOwner; /* * Clean out still-open index scans (not necessary if aborting) and *************** *** 514,520 **** { if (isCommit) inv_close(cookies[i]); ! cookies[i] = NULL; } } --- 550,556 ---- { if (isCommit) inv_close(cookies[i]); ! deleteLOfd(i); } } *************** *** 522,534 **** cookies = NULL; cookies_size = 0; ! MemoryContextSwitchTo(currentContext); ! /* Release the LO memory context to prevent permanent memory leaks. */ MemoryContextDelete(fscxt); fscxt = NULL; } /***************************************************************************** * Support routines for this file --- 558,631 ---- cookies = NULL; cookies_size = 0; ! CurrentResourceOwner = currentOwner; /* Release the LO memory context to prevent permanent memory leaks. */ MemoryContextDelete(fscxt); fscxt = NULL; } + /* + * AtSubAbort_LargeObject - + * Release LOs opened/created during the aborting subtransaction. + * + * This must be called before releasing transaction locks. + */ + void + AtSubAbort_LargeObject(void) + { + TransactionId myXid; + int i; + ResourceOwner currentOwner; + + if (fscxt == NULL) + return; /* no LO operations in this xact */ + + myXid = GetCurrentTransactionId(); + currentOwner = CurrentResourceOwner; + CurrentResourceOwner = TopTransactionResourceOwner; + + for (i = 0; i < cookies_size; i++) + { + LargeObjectDesc *lo; + + if (cookies[i] == NULL || cookies[i]->xid != myXid) + continue; + + /* + * Make sure we do not call inv_close twice if it errors out + * for some reason. Better a leak than a crash. + */ + lo = cookies[i]; + deleteLOfd(i); + + inv_close(lo); + } + + CurrentResourceOwner = currentOwner; + } + + /* + * AtSubCommit_LargeObject - + * Take care of large objects at subtransaction commit + * + * Reassign LOs created/opened during the committing subtransaction + * to the parent transaction. + */ + void + AtSubCommit_LargeObject(TransactionId parentXid) + { + TransactionId myXid = GetCurrentTransactionId(); + int i; + + if (fscxt == NULL) /* no LO operations in this xact */ + return; + + for (i = 0; i < cookies_size; i++) + { + if (cookies[i] != NULL && cookies[i]->xid == myXid) + cookies[i]->xid = parentXid; + } + } /***************************************************************************** * Support routines for this file diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/storage/file/fd.c 03pgproc/src/backend/storage/file/fd.c *** 02subbegin/src/backend/storage/file/fd.c 2004-05-31 11:08:56.000000000 -0400 --- 03pgproc/src/backend/storage/file/fd.c 2004-07-26 12:50:51.000000000 -0400 *************** *** 47,54 **** --- 47,56 ---- #include #include "miscadmin.h" + #include "access/xact.h" #include "storage/fd.h" #include "storage/ipc.h" + #include "utils/memutils.h" /* *************** *** 122,127 **** --- 124,130 ---- { signed short fd; /* current FD, or VFD_CLOSED if none */ unsigned short fdstate; /* bitflags for VFD's state */ + TransactionId create_xid; /* for XACT_TEMPORARY fds, the creating Xid */ File nextFree; /* link to next free VFD, if in freelist */ File lruMoreRecently; /* doubly linked recency-of-use list */ File lruLessRecently; *************** *** 146,172 **** static int nfile = 0; /* ! * List of stdio FILEs opened with AllocateFile. * ! * Since we don't want to encourage heavy use of AllocateFile, it seems ! * OK to put a pretty small maximum limit on the number of simultaneously ! * allocated files. ! */ ! #define MAX_ALLOCATED_FILES 32 ! ! static int numAllocatedFiles = 0; ! static FILE *allocatedFiles[MAX_ALLOCATED_FILES]; ! ! /* ! * List of DIRs opened with AllocateDir. ! * ! * Since we don't have heavy use of AllocateDir, it seems OK to put a pretty ! * small maximum limit on the number of simultaneously allocated dirs. ! */ ! #define MAX_ALLOCATED_DIRS 10 ! static int numAllocatedDirs = 0; ! static DIR *allocatedDirs[MAX_ALLOCATED_DIRS]; /* * Number of temporary files opened during the current session; --- 149,179 ---- static int nfile = 0; /* ! * List of stdio FILEs and DIRs opened with AllocateFile ! * and AllocateDir. * ! * Since we don't want to encourage heavy use of AllocateFile or AllocateDir, ! * it seems OK to put a pretty small maximum limit on the number of ! * simultaneously allocated descs. ! */ ! #define MAX_ALLOCATED_DESCS 32 ! ! typedef enum { ! AllocateDescFile, ! AllocateDescDir ! } AllocateDescKind; ! ! typedef struct { ! AllocateDescKind kind; ! union { ! FILE *file; ! DIR *dir; ! } desc; ! TransactionId create_xid; ! } AllocateDesc; ! static int numAllocatedDescs = 0; ! static AllocateDesc *allocatedDescs[MAX_ALLOCATED_DESCS]; /* * Number of temporary files opened during the current session; *************** *** 499,505 **** if (FileIsNotOpen(file)) { ! while (nfile + numAllocatedFiles + numAllocatedDirs >= max_safe_fds) { if (!ReleaseLruFile()) break; --- 506,512 ---- if (FileIsNotOpen(file)) { ! while (nfile + numAllocatedDescs >= max_safe_fds) { if (!ReleaseLruFile()) break; *************** *** 759,765 **** file = AllocateVfd(); vfdP = &VfdCache[file]; ! while (nfile + numAllocatedFiles + numAllocatedDirs >= max_safe_fds) { if (!ReleaseLruFile()) break; --- 766,772 ---- file = AllocateVfd(); vfdP = &VfdCache[file]; ! while (nfile + numAllocatedDescs >= max_safe_fds) { if (!ReleaseLruFile()) break; *************** *** 876,882 **** --- 883,892 ---- /* Mark it for deletion at EOXact */ if (!interXact) + { VfdCache[file].fdstate |= FD_XACT_TEMPORARY; + VfdCache[file].create_xid = GetCurrentTransactionId(); + } return file; } *************** *** 1133,1159 **** AllocateFile(char *name, char *mode) { FILE *file; ! DO_DB(elog(LOG, "AllocateFile: Allocated %d", numAllocatedFiles)); /* ! * The test against MAX_ALLOCATED_FILES prevents us from overflowing * allocatedFiles[]; the test against max_safe_fds prevents AllocateFile * from hogging every one of the available FDs, which'd lead to infinite * looping. */ ! if (numAllocatedFiles >= MAX_ALLOCATED_FILES || ! numAllocatedFiles + numAllocatedDirs >= max_safe_fds - 1) elog(ERROR, "too many private files demanded"); TryAgain: if ((file = fopen(name, mode)) != NULL) { ! allocatedFiles[numAllocatedFiles] = file; ! numAllocatedFiles++; ! return file; } if (errno == EMFILE || errno == ENFILE) { int save_errno = errno; --- 1143,1177 ---- AllocateFile(char *name, char *mode) { FILE *file; + AllocateDesc *desc; ! DO_DB(elog(LOG, "AllocateFile: Allocated %d (%s)", numAllocatedDescs, name)); /* ! * The test against MAX_ALLOCATED_DESCS prevents us from overflowing * allocatedFiles[]; the test against max_safe_fds prevents AllocateFile * from hogging every one of the available FDs, which'd lead to infinite * looping. */ ! if (numAllocatedDescs >= MAX_ALLOCATED_DESCS || ! numAllocatedDescs >= max_safe_fds - 1) elog(ERROR, "too many private files demanded"); + desc = (AllocateDesc *) MemoryContextAlloc(TopMemoryContext, sizeof(AllocateDesc)); + TryAgain: if ((file = fopen(name, mode)) != NULL) { ! desc->create_xid = GetCurrentTransactionId(); ! desc->desc.file = file; ! desc->kind = AllocateDescFile; ! allocatedDescs[numAllocatedDescs] = desc; ! numAllocatedDescs++; ! return desc->desc.file; } + pfree(desc); + if (errno == EMFILE || errno == ENFILE) { int save_errno = errno; *************** *** 1181,1195 **** { int i; ! DO_DB(elog(LOG, "FreeFile: Allocated %d", numAllocatedFiles)); /* Remove file from list of allocated files, if it's present */ ! for (i = numAllocatedFiles; --i >= 0;) { ! if (allocatedFiles[i] == file) { ! numAllocatedFiles--; ! allocatedFiles[i] = allocatedFiles[numAllocatedFiles]; break; } } --- 1199,1215 ---- { int i; ! DO_DB(elog(LOG, "FreeFile: Allocated %d", numAllocatedDescs)); /* Remove file from list of allocated files, if it's present */ ! for (i = numAllocatedDescs; --i >= 0;) { ! AllocateDesc *desc = allocatedDescs[i]; ! if (desc->kind == AllocateDescFile && desc->desc.file == file) { ! numAllocatedDescs--; ! allocatedDescs[i] = allocatedDescs[numAllocatedDescs]; ! pfree(desc); break; } } *************** *** 1212,1238 **** AllocateDir(const char *dirname) { DIR *dir; ! DO_DB(elog(LOG, "AllocateDir: Allocated %d", numAllocatedDirs)); /* ! * The test against MAX_ALLOCATED_DIRS prevents us from overflowing ! * allocatedDirs[]; the test against max_safe_fds prevents AllocateDir * from hogging every one of the available FDs, which'd lead to infinite * looping. */ ! if (numAllocatedDirs >= MAX_ALLOCATED_DIRS || ! numAllocatedDirs + numAllocatedFiles >= max_safe_fds - 1) elog(ERROR, "too many private dirs demanded"); TryAgain: if ((dir = opendir(dirname)) != NULL) { ! allocatedDirs[numAllocatedDirs] = dir; ! numAllocatedDirs++; ! return dir; } if (errno == EMFILE || errno == ENFILE) { int save_errno = errno; --- 1232,1266 ---- AllocateDir(const char *dirname) { DIR *dir; + AllocateDesc *desc; ! DO_DB(elog(LOG, "AllocateDir: Allocated %d (%s)", numAllocatedDescs, dirname)); /* ! * The test against MAX_ALLOCATED_DESCS prevents us from overflowing ! * allocatedDescs[]; the test against max_safe_fds prevents AllocateDir * from hogging every one of the available FDs, which'd lead to infinite * looping. */ ! if (numAllocatedDescs >= MAX_ALLOCATED_DESCS || ! numAllocatedDescs >= max_safe_fds - 1) elog(ERROR, "too many private dirs demanded"); + desc = (AllocateDesc *) MemoryContextAlloc(TopMemoryContext, sizeof(AllocateDesc)); + TryAgain: if ((dir = opendir(dirname)) != NULL) { ! desc->kind = AllocateDescDir; ! desc->desc.dir = dir; ! desc->create_xid = GetCurrentTransactionId(); ! allocatedDescs[numAllocatedDescs] = desc; ! numAllocatedDescs++; ! return desc->desc.dir; } + pfree(desc); + if (errno == EMFILE || errno == ENFILE) { int save_errno = errno; *************** *** 1260,1274 **** { int i; ! DO_DB(elog(LOG, "FreeDir: Allocated %d", numAllocatedDirs)); /* Remove dir from list of allocated dirs, if it's present */ ! for (i = numAllocatedDirs; --i >= 0;) { ! if (allocatedDirs[i] == dir) { ! numAllocatedDirs--; ! allocatedDirs[i] = allocatedDirs[numAllocatedDirs]; break; } } --- 1288,1305 ---- { int i; ! DO_DB(elog(LOG, "FreeDir: Allocated %d", numAllocatedDescs)); /* Remove dir from list of allocated dirs, if it's present */ ! for (i = numAllocatedDescs; --i >= 0;) { ! AllocateDesc *desc = allocatedDescs[i]; ! ! if (desc->kind == AllocateDescDir && desc->desc.dir == dir) { ! numAllocatedDescs--; ! allocatedDescs[i] = allocatedDescs[numAllocatedDescs]; ! pfree(desc); break; } } *************** *** 1278,1283 **** --- 1309,1337 ---- return closedir(dir); } + /* + * Free an AllocateDesc. + * + * This only calls FreeDir or FreeFile. It's used to cleanup at + * transaction end. + */ + static int + FreeDesc(AllocateDesc *desc) + { + switch (desc->kind) + { + case AllocateDescFile: + return FreeFile(desc->desc.file); + case AllocateDescDir: + return FreeDir(desc->desc.dir); + default: + elog(ERROR, "AllocateDesc kind not recognized"); + } + + /* keep compiler quiet */ + return 0; + } + /* * closeAllVfds *************** *** 1303,1308 **** --- 1357,1402 ---- } /* + * AtEOSubXact_Files + * + * Take care of subtransaction commit/abort. At abort, we close the files + * that the subtransaction may have opened. At commit, we reassign the + * files that were opened to the parent transaction. + */ + void + AtEOSubXact_Files(bool isCommit, TransactionId myXid, TransactionId parentXid) + { + Index i; + + if (SizeVfdCache > 0) + { + Assert(FileIsNotOpen(0)); /* Make sure ring not corrupted */ + for (i = 1; i < SizeVfdCache; i++) + { + if (VfdCache[i].create_xid == myXid) + { + if (isCommit) + VfdCache[i].create_xid = parentXid; + else if (VfdCache[i].fileName != NULL) + FileClose(i); + } + } + } + + for (i = 0; i < numAllocatedDescs; i++) + { + if (allocatedDescs[i]->create_xid == myXid) + { + if (isCommit) + allocatedDescs[i]->create_xid = parentXid; + else + /* have to recheck the item after FreeDesc */ + FreeDesc(allocatedDescs[i--]); + } + } + } + + /* * AtEOXact_Files * * This routine is called during transaction commit or abort (it doesn't *************** *** 1362,1372 **** } } ! while (numAllocatedFiles > 0) ! FreeFile(allocatedFiles[0]); ! ! while (numAllocatedDirs > 0) ! FreeDir(allocatedDirs[0]); } --- 1456,1463 ---- } } ! while (numAllocatedDescs > 0) ! FreeDesc(allocatedDescs[0]); } diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/storage/ipc/sinval.c 03pgproc/src/backend/storage/ipc/sinval.c *** 02subbegin/src/backend/storage/ipc/sinval.c 2004-07-23 21:51:48.000000000 -0400 --- 03pgproc/src/backend/storage/ipc/sinval.c 2004-07-26 15:19:13.000000000 -0400 *************** *** 27,32 **** --- 27,56 ---- #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_by_neg_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_by_neg_xid_inc xc_by_neg_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_by_neg_xid_inc + #define xc_slow_answer_inc + #endif /* XIDCACHE_DEBUG */ /* * Because backends sitting idle will not be reading sinval events, we *************** *** 80,85 **** --- 104,113 ---- 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) --- 472,479 ---- * * 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++) { --- 481,505 ---- bool result = false; SISeg *segP = shmInvalBuffer; ProcState *stateP = segP->procState; ! int i, ! j; int nxids = 0; TransactionId *xids; + bool locked; + + /* + * 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; --- 513,613 ---- 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[nxids++] = pxid; /* ! * Step 2a: 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; } } ! /* ! * Step 2b: check the known-not-running Xid arrays ! */ ! for (j = 0; j < PGPROC_MAX_CACHED_NEGXIDS; j++) ! { ! pxid = proc->cache.negxids[j]; ! ! if (!TransactionIdIsValid(pxid)) ! continue; ! ! if (TransactionIdEquals(pxid, xid)) ! { ! xc_by_neg_xid_inc; ! result = false; ! goto result_known; ! } ! } } } LWLockRelease(SInvalLock); + locked = false; /* * 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 < nxids; i++) ! { ! if (TransactionIdEquals(xids[i], xid)) { ! result = true; ! goto result_known; } } + result_known: ; + + if (locked) + LWLockRelease(SInvalLock); + pfree(xids); return result; *************** *** 928,930 **** --- 996,1094 ---- return count; } + + /* + * XidCacheAddRunningXid + * + * Add a TransactionId to the list of known-running transactions. + * If there is no space in the cache, just return. + */ + void + XidCacheAddRunningXid(TransactionId xid) + { + if (MyProc->cache.nxids >= PGPROC_MAX_CACHED_SUBXIDS) + return; + + TransactionIdStore(xid, &(MyProc->cache.xids[MyProc->cache.nxids])); + + MyProc->cache.nxids ++; + } + + /* + * XidCacheRemoveRunningXids + * + * Remove a bunch of TransactionIds from the list of known-running + * transactions. At the same time, add them to the list of known- + * not-running transactions. + * + * The known-not-running list uses a round-robin method, so we overwrite + * old TransactionIds. + */ + void + XidCacheRemoveRunningXids(int nxids, TransactionId *xids) + { + int i, j; + + if (nxids == 0) + return; + + for (i = 0; i < nxids; i++) + { + for (j = 0; j < PGPROC_MAX_CACHED_SUBXIDS; j++) + { + if (TransactionIdEquals(MyProc->cache.xids[j], xids[i])) + { + TransactionIdStore(InvalidTransactionId, &(MyProc->cache.xids[j])); + MyProc->cache.nxids --; + break; + } + } + TransactionIdStore(xids[i], &(MyProc->cache.negxids[MyProc->cache.negpos])); + MyProc->cache.negpos = (MyProc->cache.negpos + 1) % PGPROC_MAX_CACHED_NEGXIDS; + } + } + + /* + * XidCacheAddNotRunningXid + * + * Add a TransactionId to the list of known-not-running transactions. + * This should be only be called for Xids known not to be in the + * subxid cache, i.e., the main Xid for a transaction. + */ + void + XidCacheAddNotRunningXid(TransactionId xid) + { + TransactionIdStore(xid, &(MyProc->cache.negxids[MyProc->cache.negpos])); + MyProc->cache.negpos = (MyProc->cache.negpos + 1) % PGPROC_MAX_CACHED_NEGXIDS; + } + + #ifdef XIDCACHE_DEBUG + static void + DisplayXidCache(int code, Datum arg) + { + int i; + fprintf(stderr,"XidCache: xmin: %d, mainxid: %d, childxid: %d negxid: %d slow: %d\n", + xc_by_recent_xmin, + xc_by_main_xid, + xc_by_child_xid, + xc_by_neg_xid, + xc_slow_answer); + + if (MyProc == NULL) + return; + + fprintf(stderr, "children:\t"); + for (i = 0; i < PGPROC_MAX_CACHED_SUBXIDS; i++) + { + fprintf(stderr, "%d ", MyProc->cache.xids[i]); + } + fprintf(stderr, "\n"); + + fprintf(stderr,"negcache:\t"); + for (i = 0; i < PGPROC_MAX_CACHED_NEGXIDS; i++) + { + fprintf(stderr, "%d ", MyProc->cache.negxids[i]); + } + fprintf(stderr, "\n"); + } + #endif /* XIDCACHE_DEBUG */ diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/storage/large_object/inv_api.c 03pgproc/src/backend/storage/large_object/inv_api.c *** 02subbegin/src/backend/storage/large_object/inv_api.c 2004-07-16 01:40:09.000000000 -0400 --- 03pgproc/src/backend/storage/large_object/inv_api.c 2004-07-26 15:40:39.824944976 -0400 *************** *** 23,28 **** --- 23,29 ---- #include "access/heapam.h" #include "access/htup.h" #include "access/tuptoaster.h" + #include "access/xact.h" #include "catalog/catalog.h" #include "catalog/catname.h" #include "catalog/heap.h" *************** *** 92,97 **** --- 93,99 ---- retval = (LargeObjectDesc *) palloc(sizeof(LargeObjectDesc)); retval->id = file_oid; + retval->xid = GetCurrentTransactionId(); retval->offset = 0; if (flags & INV_WRITE) *************** *** 131,136 **** --- 133,139 ---- retval = (LargeObjectDesc *) palloc(sizeof(LargeObjectDesc)); retval->id = lobjId; + retval->xid = GetCurrentTransactionId(); retval->offset = 0; if (flags & INV_WRITE) diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/storage/lmgr/lmgr.c 03pgproc/src/backend/storage/lmgr/lmgr.c *** 02subbegin/src/backend/storage/lmgr/lmgr.c 2004-07-07 21:08:26.000000000 -0400 --- 03pgproc/src/backend/storage/lmgr/lmgr.c 2004-07-26 15:33:21.000000000 -0400 *************** *** 137,143 **** tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = InvalidBlockNumber; ! if (!LockAcquire(LockTableId, &tag, GetCurrentTransactionId(), lockmode, false)) elog(ERROR, "LockAcquire failed"); --- 137,143 ---- tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = InvalidBlockNumber; ! if (!LockAcquire(LockTableId, &tag, GetTopTransactionId(), lockmode, false)) elog(ERROR, "LockAcquire failed"); *************** *** 171,177 **** tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = InvalidBlockNumber; ! if (!LockAcquire(LockTableId, &tag, GetCurrentTransactionId(), lockmode, true)) return false; --- 171,177 ---- tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = InvalidBlockNumber; ! if (!LockAcquire(LockTableId, &tag, GetTopTransactionId(), lockmode, true)) return false; *************** *** 201,207 **** tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = InvalidBlockNumber; ! LockRelease(LockTableId, &tag, GetCurrentTransactionId(), lockmode); } /* --- 201,207 ---- tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = InvalidBlockNumber; ! LockRelease(LockTableId, &tag, GetTopTransactionId(), lockmode); } /* *************** *** 264,270 **** tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = blkno; ! if (!LockAcquire(LockTableId, &tag, GetCurrentTransactionId(), lockmode, false)) elog(ERROR, "LockAcquire failed"); } --- 264,270 ---- tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = blkno; ! if (!LockAcquire(LockTableId, &tag, GetTopTransactionId(), lockmode, false)) elog(ERROR, "LockAcquire failed"); } *************** *** 285,291 **** tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = blkno; ! return LockAcquire(LockTableId, &tag, GetCurrentTransactionId(), lockmode, true); } --- 285,291 ---- tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = blkno; ! return LockAcquire(LockTableId, &tag, GetTopTransactionId(), lockmode, true); } *************** *** 302,308 **** tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = blkno; ! LockRelease(LockTableId, &tag, GetCurrentTransactionId(), lockmode); } /* --- 302,308 ---- tag.dbId = relation->rd_lockInfo.lockRelId.dbId; tag.objId.blkno = blkno; ! LockRelease(LockTableId, &tag, GetTopTransactionId(), lockmode); } /* diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/tcop/dest.c 03pgproc/src/backend/tcop/dest.c *** 02subbegin/src/backend/tcop/dest.c 2003-12-18 00:56:26.000000000 -0300 --- 03pgproc/src/backend/tcop/dest.c 2004-07-25 18:26:32.000000000 -0400 *************** *** 36,41 **** --- 36,43 ---- #include "utils/portal.h" + static void SendTransactionNestLevel(void); + /* ---------------- * dummy DestReceiver functions * ---------------- *************** *** 213,218 **** --- 215,221 ---- { StringInfoData buf; + SendTransactionNestLevel(); pq_beginmessage(&buf, 'Z'); pq_sendbyte(&buf, TransactionBlockStatusCode()); pq_endmessage(&buf); *************** *** 230,232 **** --- 233,251 ---- break; } } + + static void + SendTransactionNestLevel(void) + { + char *val = (char *)palloc(12); + StringInfoData msgbuf; + + snprintf(val, 12, "%d", GetCurrentTransactionNestLevel()); + + pq_beginmessage(&msgbuf, 'S'); + pq_sendstring(&msgbuf, "nesting_level"); + pq_sendstring(&msgbuf, val); + pq_endmessage(&msgbuf); + + pfree(val); + } diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/tcop/utility.c 03pgproc/src/backend/tcop/utility.c *** 02subbegin/src/backend/tcop/utility.c 2004-07-26 12:42:04.000000000 -0400 --- 03pgproc/src/backend/tcop/utility.c 2004-07-26 11:46:15.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 02subbegin/src/backend/utils/misc/guc.c 03pgproc/src/backend/utils/misc/guc.c *** 02subbegin/src/backend/utils/misc/guc.c 2004-07-22 14:31:58.000000000 -0400 --- 03pgproc/src/backend/utils/misc/guc.c 2004-07-26 11:49:01.000000000 -0400 *************** *** 5439,5448 **** static bool assign_transaction_read_only(bool newval, bool doit, GucSource source) { ! if (doit && source >= PGC_S_INTERACTIVE && IsSubTransaction()) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), ! errmsg("cannot set transaction read only mode inside a subtransaction"))); return true; } --- 5439,5449 ---- static bool assign_transaction_read_only(bool newval, bool doit, GucSource source) { ! if (doit && source >= PGC_S_INTERACTIVE && IsSubTransaction() && ! newval == false && XactReadOnly) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), ! errmsg("cannot set transaction read-write mode inside a read only transaction"))); return true; } diff -Ncr --exclude-from=diff-ignore 02subbegin/src/backend/utils/time/tqual.c 03pgproc/src/backend/utils/time/tqual.c *** 02subbegin/src/backend/utils/time/tqual.c 2004-07-05 16:06:28.000000000 -0400 --- 03pgproc/src/backend/utils/time/tqual.c 2004-07-24 15:23:04.000000000 -0400 *************** *** 118,124 **** --- 118,127 ---- /* deleting subtransaction aborted */ if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple))) + { + tuple->t_infomask |= HEAP_XMAX_INVALID; return true; + } Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple))); *************** *** 268,274 **** --- 271,280 ---- /* deleting subtransaction aborted */ if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple))) + { + tuple->t_infomask |= HEAP_XMAX_INVALID; return true; + } Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple))); *************** *** 452,458 **** --- 458,467 ---- /* deleting subtransaction aborted */ if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple))) + { + tuple->t_infomask |= HEAP_XMAX_INVALID; return HeapTupleMayBeUpdated; + } Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple))); *************** *** 590,596 **** --- 599,608 ---- /* deleting subtransaction aborted */ if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple))) + { + tuple->t_infomask |= HEAP_XMAX_INVALID; return true; + } Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple))); *************** *** 732,738 **** --- 744,753 ---- /* deleting subtransaction aborted */ /* FIXME -- is this correct w.r.t. the cmax of the tuple? */ if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple))) + { + tuple->t_infomask |= HEAP_XMAX_INVALID; return true; + } Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple))); *************** *** 757,776 **** /* * By here, the inserting transaction has committed - have to check * when... */ if (TransactionIdFollowsOrEquals(HeapTupleHeaderGetXmin(tuple), snapshot->xmin)) { uint32 i; ! if (TransactionIdFollowsOrEquals(HeapTupleHeaderGetXmin(tuple), ! snapshot->xmax)) return false; for (i = 0; i < snapshot->xcnt; i++) { ! if (SubTransXidsHaveCommonAncestor(HeapTupleHeaderGetXmin(tuple), ! snapshot->xip[i])) return false; } } --- 772,795 ---- /* * By here, the inserting transaction has committed - have to check * when... + * + * Note that we use the topmost transaction Id instead of the real + * Xmin of the tuple. */ if (TransactionIdFollowsOrEquals(HeapTupleHeaderGetXmin(tuple), snapshot->xmin)) { uint32 i; + TransactionId parentXid; ! parentXid = SubTransGetTopmostTransaction(HeapTupleHeaderGetXmin(tuple)); ! ! if (TransactionIdFollowsOrEquals(parentXid, snapshot->xmax)) return false; for (i = 0; i < snapshot->xcnt; i++) { ! if (TransactionIdEquals(parentXid, snapshot->xip[i])) return false; } } *************** *** 808,820 **** if (TransactionIdFollowsOrEquals(HeapTupleHeaderGetXmax(tuple), snapshot->xmin)) { uint32 i; ! if (TransactionIdFollowsOrEquals(HeapTupleHeaderGetXmax(tuple), ! snapshot->xmax)) return true; for (i = 0; i < snapshot->xcnt; i++) { ! if (SubTransXidsHaveCommonAncestor(HeapTupleHeaderGetXmax(tuple), snapshot->xip[i])) return true; } } --- 827,842 ---- if (TransactionIdFollowsOrEquals(HeapTupleHeaderGetXmax(tuple), snapshot->xmin)) { uint32 i; + TransactionId parentXid; + + parentXid = SubTransGetTopmostTransaction(HeapTupleHeaderGetXmax(tuple)); ! if (TransactionIdFollowsOrEquals(parentXid, snapshot->xmax)) return true; + for (i = 0; i < snapshot->xcnt; i++) { ! if (TransactionIdEquals(parentXid, snapshot->xip[i])) return true; } } diff -Ncr --exclude-from=diff-ignore 02subbegin/src/bin/psql/prompt.c 03pgproc/src/bin/psql/prompt.c *** 02subbegin/src/bin/psql/prompt.c 2004-03-21 13:54:49.000000000 -0400 --- 03pgproc/src/bin/psql/prompt.c 2004-07-16 02:39:33.000000000 -0400 *************** *** 46,51 **** --- 46,52 ---- * in prompt2 -, *, ', or "; * in prompt3 nothing * %x - transaction status: empty, *, !, ? (unknown or no connection) + * %d - transaction nesting level * %? - the error code of the last query (not yet implemented) * %% - a percent sign * *************** *** 237,242 **** --- 238,250 ---- } break; + case 'd': + if (!pset.db) + buf[0] = '?'; + else + snprintf(buf, MAX_PROMPT_SIZE, "%d", PQnestingLevel(pset.db)); + break; + case '?': /* not here yet */ break; diff -Ncr --exclude-from=diff-ignore 02subbegin/src/bin/psql/tab-complete.c 03pgproc/src/bin/psql/tab-complete.c *** 02subbegin/src/bin/psql/tab-complete.c 2004-07-25 16:31:27.000000000 -0400 --- 03pgproc/src/bin/psql/tab-complete.c 2004-07-24 13:12:38.000000000 -0400 *************** *** 722,728 **** else if (pg_strcasecmp(prev2_wd, "ANALYZE") == 0) COMPLETE_WITH_CONST(";"); ! /* BEGIN, COMMIT, ABORT */ else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 || pg_strcasecmp(prev_wd, "END") == 0 || pg_strcasecmp(prev_wd, "COMMIT") == 0 || --- 722,728 ---- else if (pg_strcasecmp(prev2_wd, "ANALYZE") == 0) COMPLETE_WITH_CONST(";"); ! /* BEGIN, END, COMMIT, ABORT */ else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 || pg_strcasecmp(prev_wd, "END") == 0 || pg_strcasecmp(prev_wd, "COMMIT") == 0 || diff -Ncr --exclude-from=diff-ignore 02subbegin/src/include/catalog/namespace.h 03pgproc/src/include/catalog/namespace.h *** 02subbegin/src/include/catalog/namespace.h 2004-01-19 19:29:12.000000000 -0300 --- 03pgproc/src/include/catalog/namespace.h 2004-07-16 14:32:46.000000000 -0400 *************** *** 91,96 **** --- 91,97 ---- /* initialization & transaction cleanup code */ extern void InitializeSearchPath(void); extern void AtEOXact_Namespace(bool isCommit); + extern void AtEOSubXact_Namespace(bool isCommit, TransactionId parentXid); /* stuff for search_path GUC variable */ extern char *namespace_search_path; diff -Ncr --exclude-from=diff-ignore 02subbegin/src/include/commands/user.h 03pgproc/src/include/commands/user.h *** 02subbegin/src/include/commands/user.h 2004-01-15 23:32:29.000000000 -0300 --- 03pgproc/src/include/commands/user.h 2004-07-15 15:39:49.000000000 -0400 *************** *** 32,36 **** --- 32,37 ---- extern Datum update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS); extern void AtEOXact_UpdatePasswordFile(bool isCommit); + extern void AtEOSubXact_UpdatePasswordFile(bool isCommit, TransactionId parentXid); #endif /* USER_H */ diff -Ncr --exclude-from=diff-ignore 02subbegin/src/include/libpq/be-fsstubs.h 03pgproc/src/include/libpq/be-fsstubs.h *** 02subbegin/src/include/libpq/be-fsstubs.h 2004-07-16 01:40:09.000000000 -0400 --- 03pgproc/src/include/libpq/be-fsstubs.h 2004-07-17 23:34:32.000000000 -0400 *************** *** 45,50 **** /* * Cleanup LOs at xact commit/abort [ Pascal André ] */ ! extern void lo_commit(bool isCommit); #endif /* BE_FSSTUBS_H */ --- 45,52 ---- /* * Cleanup LOs at xact commit/abort [ Pascal André ] */ ! extern void AtEOXact_LargeObject(bool isCommit); ! extern void AtSubAbort_LargeObject(void); ! extern void AtSubCommit_LargeObject(TransactionId parentXid); #endif /* BE_FSSTUBS_H */ diff -Ncr --exclude-from=diff-ignore 02subbegin/src/include/storage/fd.h 03pgproc/src/include/storage/fd.h *** 02subbegin/src/include/storage/fd.h 2004-05-31 11:08:58.000000000 -0400 --- 03pgproc/src/include/storage/fd.h 2004-07-24 16:08:19.000000000 -0400 *************** *** 85,90 **** --- 85,92 ---- extern void set_max_safe_fds(void); extern void closeAllVfds(void); extern void AtEOXact_Files(void); + extern void AtEOSubXact_Files(bool isCommit, TransactionId myXid, + TransactionId parentXid); extern void RemovePgTempFiles(void); extern int pg_fsync(int fd); extern int pg_fdatasync(int fd); diff -Ncr --exclude-from=diff-ignore 02subbegin/src/include/storage/large_object.h 03pgproc/src/include/storage/large_object.h *** 02subbegin/src/include/storage/large_object.h 2004-07-16 01:40:09.000000000 -0400 --- 03pgproc/src/include/storage/large_object.h 2004-07-15 23:33:45.000000000 -0400 *************** *** 22,27 **** --- 22,28 ---- * Data about a currently-open large object. * * id is the logical OID of the large object + * xid is the transaction Id that opened the LO * offset is the current seek offset within the LO * heap_r holds an open-relation reference to pg_largeobject * index_r holds an open-relation reference to pg_largeobject_loid_pn_index *************** *** 33,38 **** --- 34,40 ---- typedef struct LargeObjectDesc { Oid id; + TransactionId xid; uint32 offset; /* current seek pointer */ int flags; /* locking info, etc */ diff -Ncr --exclude-from=diff-ignore 02subbegin/src/include/storage/proc.h 03pgproc/src/include/storage/proc.h *** 02subbegin/src/include/storage/proc.h 2004-07-22 14:32:04.000000000 -0400 --- 03pgproc/src/include/storage/proc.h 2004-07-22 22:28:55.000000000 -0400 *************** *** 19,24 **** --- 19,46 ---- #include "storage/lock.h" #include "storage/pg_sema.h" + /* + * XXX These numbers are made up ... + */ + #define PGPROC_MAX_CACHED_SUBXIDS 20 + #define PGPROC_MAX_CACHED_NEGXIDS 40 + + /* + * Each backend keeps track of (some of) its subtransactions' + * TransactionIds in the PGPROC struct. This allows for a performance + * improvement in TransactionIdIsInProgress. Note that the main Xid + * for a transaction is not recorded here (though this is not critical). + * + * Also we keep a group of transactions known _not_ to be in progress. + */ + struct XidCache { + /* running Xids cache */ + int nxids; + TransactionId xids[PGPROC_MAX_CACHED_SUBXIDS]; + /* negative Xid cache */ + TransactionId negxids[PGPROC_MAX_CACHED_NEGXIDS]; + int negpos; /* allocator position */ + }; /* * Each backend has a PGPROC struct in shared memory. There is also a list of *************** *** 39,44 **** --- 61,68 ---- 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 02subbegin/src/include/storage/sinval.h 03pgproc/src/include/storage/sinval.h *** 02subbegin/src/include/storage/sinval.h 2004-06-03 15:59:03.000000000 -0400 --- 03pgproc/src/include/storage/sinval.h 2004-07-25 18:03:15.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); + extern void XidCacheAddNotRunningXid(TransactionId xid); + #endif /* SINVAL_H */ diff -Ncr --exclude-from=diff-ignore 02subbegin/src/interfaces/libpq/fe-connect.c 03pgproc/src/interfaces/libpq/fe-connect.c *** 02subbegin/src/interfaces/libpq/fe-connect.c 2004-07-14 19:18:09.000000000 -0400 --- 03pgproc/src/interfaces/libpq/fe-connect.c 2004-07-16 02:35:31.000000000 -0400 *************** *** 2847,2852 **** --- 2847,2860 ---- return conn->xactStatus; } + int + PQnestingLevel(const PGconn *conn) + { + if (!conn || conn->status != CONNECTION_OK) + return -1; + return conn->nestingLevel; + } + const char * PQparameterStatus(const PGconn *conn, const char *paramName) { diff -Ncr --exclude-from=diff-ignore 02subbegin/src/interfaces/libpq/fe-exec.c 03pgproc/src/interfaces/libpq/fe-exec.c *** 02subbegin/src/interfaces/libpq/fe-exec.c 2004-05-12 00:06:17.000000000 -0400 --- 03pgproc/src/interfaces/libpq/fe-exec.c 2004-07-16 02:59:53.000000000 -0400 *************** *** 613,618 **** --- 613,620 ---- */ if (strcmp(name, "client_encoding") == 0) conn->client_encoding = pg_char_to_encoding(value); + else if (strcmp(name, "nesting_level") == 0) + conn->nestingLevel = atoi(value); else if (strcmp(name, "server_version") == 0) { int cnt; diff -Ncr --exclude-from=diff-ignore 02subbegin/src/interfaces/libpq/libpq-fe.h 03pgproc/src/interfaces/libpq/libpq-fe.h *** 02subbegin/src/interfaces/libpq/libpq-fe.h 2004-03-26 15:27:52.000000000 -0400 --- 03pgproc/src/interfaces/libpq/libpq-fe.h 2004-07-16 02:37:06.000000000 -0400 *************** *** 245,250 **** --- 245,251 ---- extern char *PQoptions(const PGconn *conn); extern ConnStatusType PQstatus(const PGconn *conn); extern PGTransactionStatusType PQtransactionStatus(const PGconn *conn); + extern int PQnestingLevel(const PGconn *conn); extern const char *PQparameterStatus(const PGconn *conn, const char *paramName); extern int PQprotocolVersion(const PGconn *conn); diff -Ncr --exclude-from=diff-ignore 02subbegin/src/interfaces/libpq/libpq-int.h 03pgproc/src/interfaces/libpq/libpq-int.h *** 02subbegin/src/interfaces/libpq/libpq-int.h 2004-06-03 15:59:05.000000000 -0400 --- 03pgproc/src/interfaces/libpq/libpq-int.h 2004-07-16 02:36:07.000000000 -0400 *************** *** 265,270 **** --- 265,271 ---- PGAsyncStatusType asyncStatus; PGTransactionStatusType xactStatus; /* note: xactStatus never changes to ACTIVE */ + int nestingLevel; /* transaction nesting level */ bool nonblocking; /* whether this connection is using * nonblock sending semantics */ bool ext_query; /* was our last query sent with extended