Nested transactions. There is still much left to do, especially on the

performance front, but with feature freeze upon us I think it's time to
drive a stake in the ground and say that this will be in 7.5.

Alvaro Herrera, with some help from Tom Lane.
This commit is contained in:
Tom Lane 2004-07-01 00:52:04 +00:00
parent 4c9aa572fa
commit 573a71a5da
74 changed files with 4516 additions and 1144 deletions

View File

@ -75,8 +75,7 @@ user_write_unlock_oid(Oid oid)
int
user_unlock_all(void)
{
return LockReleaseAll(USER_LOCKMETHOD, MyProc, false,
InvalidTransactionId);
return LockReleaseAll(USER_LOCKMETHOD, MyProc, ReleaseAll, 0, NULL);
}
/* end of file */

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/gist/gistscan.c,v 1.51 2004/01/07 18:56:23 neilc Exp $
* $PostgreSQL: pgsql/src/backend/access/gist/gistscan.c,v 1.52 2004/07/01 00:49:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -41,6 +41,7 @@ static void adjustiptr(IndexScanDesc s, ItemPointer iptr,
typedef struct GISTScanListData
{
IndexScanDesc gsl_scan;
TransactionId gsl_creatingXid;
struct GISTScanListData *gsl_next;
} GISTScanListData;
@ -223,6 +224,7 @@ gistregscan(IndexScanDesc s)
l = (GISTScanList) palloc(sizeof(GISTScanListData));
l->gsl_scan = s;
l->gsl_creatingXid = GetCurrentTransactionId();
l->gsl_next = GISTScans;
GISTScans = l;
}
@ -271,6 +273,46 @@ AtEOXact_gist(void)
GISTScans = NULL;
}
/*
* AtEOSubXact_gist() --- clean up gist subsystem at subxact abort or commit.
*
* This is here because it needs to touch this module's static var GISTScans.
*/
void
AtEOSubXact_gist(TransactionId childXid)
{
GISTScanList l;
GISTScanList prev;
GISTScanList next;
/*
* Note: these actions should only be necessary during xact abort; but
* they can't hurt during a commit.
*/
/*
* Forget active scans that were started in this subtransaction.
*/
prev = NULL;
for (l = GISTScans; l != NULL; l = next)
{
next = l->gsl_next;
if (l->gsl_creatingXid == childXid)
{
if (prev == NULL)
GISTScans = next;
else
prev->gsl_next = next;
pfree(l);
/* prev does not change */
}
else
prev = l;
}
}
void
gistadjscans(Relation rel, int op, BlockNumber blkno, OffsetNumber offnum)
{

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/hash/hashscan.c,v 1.33 2004/01/07 18:56:23 neilc Exp $
* $PostgreSQL: pgsql/src/backend/access/hash/hashscan.c,v 1.34 2004/07/01 00:49:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -21,6 +21,7 @@
typedef struct HashScanListData
{
IndexScanDesc hashsl_scan;
TransactionId hashsl_creatingXid;
struct HashScanListData *hashsl_next;
} HashScanListData;
@ -50,6 +51,46 @@ AtEOXact_hash(void)
HashScans = NULL;
}
/*
* AtEOSubXact_hash() --- clean up hash subsystem at subxact abort or commit.
*
* This is here because it needs to touch this module's static var HashScans.
*/
void
AtEOSubXact_hash(TransactionId childXid)
{
HashScanList l;
HashScanList prev;
HashScanList next;
/*
* Note: these actions should only be necessary during xact abort; but
* they can't hurt during a commit.
*/
/*
* Forget active scans that were started in this subtransaction.
*/
prev = NULL;
for (l = HashScans; l != NULL; l = next)
{
next = l->hashsl_next;
if (l->hashsl_creatingXid == childXid)
{
if (prev == NULL)
HashScans = next;
else
prev->hashsl_next = next;
pfree(l);
/* prev does not change */
}
else
prev = l;
}
}
/*
* _Hash_regscan() -- register a new scan.
*/
@ -60,6 +101,7 @@ _hash_regscan(IndexScanDesc scan)
new_el = (HashScanList) palloc(sizeof(HashScanListData));
new_el->hashsl_scan = scan;
new_el->hashsl_creatingXid = GetCurrentTransactionId();
new_el->hashsl_next = HashScans;
HashScans = new_el;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/rtree/rtscan.c,v 1.51 2004/01/07 18:56:24 neilc Exp $
* $PostgreSQL: pgsql/src/backend/access/rtree/rtscan.c,v 1.52 2004/07/01 00:49:31 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -42,6 +42,7 @@ static void adjustiptr(IndexScanDesc s, ItemPointer iptr,
typedef struct RTScanListData
{
IndexScanDesc rtsl_scan;
TransactionId rtsl_creatingXid;
struct RTScanListData *rtsl_next;
} RTScanListData;
@ -240,6 +241,7 @@ rtregscan(IndexScanDesc s)
l = (RTScanList) palloc(sizeof(RTScanListData));
l->rtsl_scan = s;
l->rtsl_creatingXid = GetCurrentTransactionId();
l->rtsl_next = RTScans;
RTScans = l;
}
@ -290,6 +292,46 @@ AtEOXact_rtree(void)
RTScans = NULL;
}
/*
* AtEOSubXact_rtree() --- clean up rtree subsystem at subxact abort or commit.
*
* This is here because it needs to touch this module's static var RTScans.
*/
void
AtEOSubXact_rtree(TransactionId childXid)
{
RTScanList l;
RTScanList prev;
RTScanList next;
/*
* Note: these actions should only be necessary during xact abort; but
* they can't hurt during a commit.
*/
/*
* Forget active scans that were started in this subtransaction.
*/
prev = NULL;
for (l = RTScans; l != NULL; l = next)
{
next = l->rtsl_next;
if (l->rtsl_creatingXid == childXid)
{
if (prev == NULL)
RTScans = next;
else
prev->rtsl_next = next;
pfree(l);
/* prev does not change */
}
else
prev = l;
}
}
void
rtadjscans(Relation r, int op, BlockNumber blkno, OffsetNumber offnum)
{

View File

@ -4,7 +4,7 @@
# Makefile for access/transam
#
# IDENTIFICATION
# $PostgreSQL: pgsql/src/backend/access/transam/Makefile,v 1.18 2003/11/29 19:51:40 pgsql Exp $
# $PostgreSQL: pgsql/src/backend/access/transam/Makefile,v 1.19 2004/07/01 00:49:42 tgl Exp $
#
#-------------------------------------------------------------------------
@ -12,7 +12,7 @@ subdir = src/backend/access/transam
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
OBJS = clog.o transam.o varsup.o xact.o xlog.o xlogutils.o rmgr.o slru.o
OBJS = clog.o transam.o varsup.o xact.o xlog.o xlogutils.o rmgr.o slru.o subtrans.o
all: SUBSYS.o

View File

@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/access/transam/clog.c,v 1.20 2004/05/31 03:47:54 tgl Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/clog.c,v 1.21 2004/07/01 00:49:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -21,14 +21,13 @@
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include "access/clog.h"
#include "access/slru.h"
#include "storage/lwlock.h"
#include "miscadmin.h"
#include "storage/lwlock.h"
/*
@ -65,7 +64,7 @@
* is guaranteed flushed through the XLOG commit record before we are called
* to log a commit, so the WAL rule "write xlog before data" is satisfied
* automatically for commits, and we don't really care for aborts. Therefore,
* we don't need to mark XLOG pages with LSN information; we have enough
* we don't need to mark CLOG pages with LSN information; we have enough
* synchronization already.
*----------
*/
@ -95,20 +94,22 @@ TransactionIdSetStatus(TransactionId xid, XidStatus status)
char *byteptr;
Assert(status == TRANSACTION_STATUS_COMMITTED ||
status == TRANSACTION_STATUS_ABORTED);
status == TRANSACTION_STATUS_ABORTED ||
status == TRANSACTION_STATUS_SUB_COMMITTED);
LWLockAcquire(ClogCtl->ControlLock, LW_EXCLUSIVE);
byteptr = SimpleLruReadPage(ClogCtl, pageno, xid, true);
byteptr += byteno;
/* Current state should be 0 or target state */
/* Current state should be 0, subcommitted or target state */
Assert(((*byteptr >> bshift) & CLOG_XACT_BITMASK) == 0 ||
((*byteptr >> bshift) & CLOG_XACT_BITMASK) == TRANSACTION_STATUS_SUB_COMMITTED ||
((*byteptr >> bshift) & CLOG_XACT_BITMASK) == status);
*byteptr |= (status << bshift);
/* ...->page_status[slotno] = CLOG_PAGE_DIRTY; already done */
/* ...->page_status[slotno] = SLRU_PAGE_DIRTY; already done */
LWLockRelease(ClogCtl->ControlLock);
}
@ -117,7 +118,7 @@ TransactionIdSetStatus(TransactionId xid, XidStatus status)
* Interrogate the state of a transaction in the commit log.
*
* NB: this is a low-level routine and is NOT the preferred entry point
* for most uses; TransactionLogTest() in transam.c is the intended caller.
* for most uses; TransactionLogFetch() in transam.c is the intended caller.
*/
XidStatus
TransactionIdGetStatus(TransactionId xid)
@ -176,7 +177,7 @@ BootStrapCLOG(void)
/* Make sure it's written out */
SimpleLruWritePage(ClogCtl, slotno, NULL);
/* Assert(ClogCtl->page_status[slotno] == CLOG_PAGE_CLEAN); */
/* Assert(ClogCtl->page_status[slotno] == SLRU_PAGE_CLEAN); */
LWLockRelease(ClogCtl->ControlLock);
}
@ -211,7 +212,8 @@ StartupCLOG(void)
/*
* Initialize our idea of the latest page number.
*/
SimpleLruSetLatestPage(ClogCtl, TransactionIdToPage(ShmemVariableCache->nextXid));
SimpleLruSetLatestPage(ClogCtl,
TransactionIdToPage(ShmemVariableCache->nextXid));
}
/*
@ -333,51 +335,20 @@ WriteZeroPageXlogRec(int pageno)
rdata.data = (char *) (&pageno);
rdata.len = sizeof(int);
rdata.next = NULL;
(void) XLogInsert(RM_CLOG_ID, CLOG_ZEROPAGE | XLOG_NO_TRAN, &rdata);
(void) XLogInsert(RM_SLRU_ID, CLOG_ZEROPAGE | XLOG_NO_TRAN, &rdata);
}
/*
* CLOG resource manager's routines
*/
/* Redo a ZEROPAGE action during WAL replay */
void
clog_redo(XLogRecPtr lsn, XLogRecord *record)
clog_zeropage_redo(int pageno)
{
uint8 info = record->xl_info & ~XLR_INFO_MASK;
int slotno;
if (info == CLOG_ZEROPAGE)
{
int pageno;
int slotno;
LWLockAcquire(ClogCtl->ControlLock, LW_EXCLUSIVE);
memcpy(&pageno, XLogRecGetData(record), sizeof(int));
slotno = ZeroCLOGPage(pageno, false);
SimpleLruWritePage(ClogCtl, slotno, NULL);
/* Assert(ClogCtl->page_status[slotno] == SLRU_PAGE_CLEAN); */
LWLockAcquire(ClogCtl->ControlLock, LW_EXCLUSIVE);
slotno = ZeroCLOGPage(pageno, false);
SimpleLruWritePage(ClogCtl, slotno, NULL);
/* Assert(ClogCtl->page_status[slotno] == SLRU_PAGE_CLEAN); */
LWLockRelease(ClogCtl->ControlLock);
}
}
void
clog_undo(XLogRecPtr lsn, XLogRecord *record)
{
}
void
clog_desc(char *buf, uint8 xl_info, char *rec)
{
uint8 info = xl_info & ~XLR_INFO_MASK;
if (info == CLOG_ZEROPAGE)
{
int pageno;
memcpy(&pageno, rec, sizeof(int));
sprintf(buf + strlen(buf), "zeropage: %d", pageno);
}
else
strcat(buf, "UNKNOWN");
LWLockRelease(ClogCtl->ControlLock);
}

View File

@ -3,16 +3,16 @@
*
* Resource managers definition
*
* $PostgreSQL: pgsql/src/backend/access/transam/rmgr.c,v 1.12 2003/11/29 19:51:40 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/rmgr.c,v 1.13 2004/07/01 00:49:42 tgl Exp $
*/
#include "postgres.h"
#include "access/clog.h"
#include "access/gist.h"
#include "access/hash.h"
#include "access/heapam.h"
#include "access/nbtree.h"
#include "access/rtree.h"
#include "access/slru.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "storage/smgr.h"
@ -23,7 +23,7 @@ RmgrData RmgrTable[RM_MAX_ID + 1] = {
{"XLOG", xlog_redo, xlog_undo, xlog_desc, NULL, NULL},
{"Transaction", xact_redo, xact_undo, xact_desc, NULL, NULL},
{"Storage", smgr_redo, smgr_undo, smgr_desc, NULL, NULL},
{"CLOG", clog_redo, clog_undo, clog_desc, NULL, NULL},
{"SLRU", slru_redo, slru_undo, slru_desc, NULL, NULL},
{"Reserved 4", NULL, NULL, NULL, NULL, NULL},
{"Reserved 5", NULL, NULL, NULL, NULL, NULL},
{"Reserved 6", NULL, NULL, NULL, NULL, NULL},

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/access/transam/slru.c,v 1.16 2004/05/31 03:47:54 tgl Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/slru.c,v 1.17 2004/07/01 00:49:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -16,8 +16,9 @@
#include <sys/stat.h>
#include <unistd.h>
#include "access/clog.h"
#include "access/slru.h"
#include "access/clog.h" /* only for NUM_CLOG_BUFFERS */
#include "access/subtrans.h"
#include "postmaster/bgwriter.h"
#include "storage/fd.h"
#include "storage/lwlock.h"
@ -1025,3 +1026,55 @@ SlruScanDirectory(SlruCtl ctl, int cutoffPage, bool doDeletions)
return found;
}
/*
* SLRU resource manager's routines
*/
void
slru_redo(XLogRecPtr lsn, XLogRecord *record)
{
uint8 info = record->xl_info & ~XLR_INFO_MASK;
int pageno;
memcpy(&pageno, XLogRecGetData(record), sizeof(int));
switch (info)
{
case CLOG_ZEROPAGE:
clog_zeropage_redo(pageno);
break;
case SUBTRANS_ZEROPAGE:
subtrans_zeropage_redo(pageno);
break;
default:
elog(PANIC, "slru_redo: unknown op code %u", info);
}
}
void
slru_undo(XLogRecPtr lsn, XLogRecord *record)
{
}
void
slru_desc(char *buf, uint8 xl_info, char *rec)
{
uint8 info = xl_info & ~XLR_INFO_MASK;
if (info == CLOG_ZEROPAGE)
{
int pageno;
memcpy(&pageno, rec, sizeof(int));
sprintf(buf + strlen(buf), "clog zeropage: %d", pageno);
}
else if (info == SUBTRANS_ZEROPAGE)
{
int pageno;
memcpy(&pageno, rec, sizeof(int));
sprintf(buf + strlen(buf), "subtrans zeropage: %d", pageno);
}
else
strcat(buf, "UNKNOWN");
}

View File

@ -0,0 +1,388 @@
/*-------------------------------------------------------------------------
*
* subtrans.c
* PostgreSQL subtrans-log manager
*
* The pg_subtrans manager is a pg_clog-like manager which stores the parent
* transaction Id for each transaction. It is a fundamental part of the
* nested transactions implementation. A main transaction has a parent
* of InvalidTransactionId, and each subtransaction has its immediate parent.
* The tree can easily be walked from child to parent, but not in the
* opposite direction.
*
* This code is mostly derived from clog.c.
*
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/access/transam/subtrans.c,v 1.1 2004/07/01 00:49:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include "access/slru.h"
#include "access/subtrans.h"
#include "miscadmin.h"
#include "storage/lwlock.h"
/*
* Defines for SubTrans page and segment sizes. A page is the same BLCKSZ
* as is used everywhere else in Postgres.
*
* Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
* SubTrans page numbering also wraps around at
* 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE, and segment numbering at
* 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE/SLRU_SEGMENTS_PER_PAGE. We need take no
* explicit notice of that fact in this module, except when comparing segment
* and page numbers in TruncateSubTrans (see SubTransPagePrecedes).
*/
/* We need four bytes per xact */
#define SUBTRANS_XACTS_PER_PAGE (BLCKSZ / sizeof(TransactionId))
#define TransactionIdToPage(xid) ((xid) / (TransactionId) SUBTRANS_XACTS_PER_PAGE)
#define TransactionIdToEntry(xid) ((xid) % (TransactionId) SUBTRANS_XACTS_PER_PAGE)
/*----------
* Shared-memory data structures for SUBTRANS control
*
* XLOG interactions: this module generates an XLOG record whenever a new
* SUBTRANS page is initialized to zeroes. Other writes of SUBTRANS come from
* recording of transaction commit or abort in xact.c, which generates its
* own XLOG records for these events and will re-perform the status update
* on redo; so we need make no additional XLOG entry here. Also, the XLOG
* is guaranteed flushed through the XLOG commit record before we are called
* to log a commit, so the WAL rule "write xlog before data" is satisfied
* automatically for commits, and we don't really care for aborts. Therefore,
* we don't need to mark SUBTRANS pages with LSN information; we have enough
* synchronization already.
*----------
*/
static SlruCtlData SubTransCtlData;
static SlruCtl SubTransCtl = &SubTransCtlData;
static int ZeroSUBTRANSPage(int pageno, bool writeXlog);
static bool SubTransPagePrecedes(int page1, int page2);
static void WriteZeroPageXlogRec(int pageno);
/*
* Record the parent of a subtransaction in the subtrans log.
*/
void
SubTransSetParent(TransactionId xid, TransactionId parent)
{
int pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
TransactionId *ptr;
LWLockAcquire(SubTransCtl->ControlLock, LW_EXCLUSIVE);
ptr = (TransactionId *) SimpleLruReadPage(SubTransCtl, pageno, xid, true);
ptr += entryno;
/* Current state should be 0 or target state */
Assert(*ptr == InvalidTransactionId || *ptr == parent);
*ptr = parent;
/* ...->page_status[slotno] = SLRU_PAGE_DIRTY; already done */
LWLockRelease(SubTransCtl->ControlLock);
}
/*
* Interrogate the parent of a transaction in the subtrans log.
*/
TransactionId
SubTransGetParent(TransactionId xid)
{
int pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
TransactionId *ptr;
TransactionId parent;
/* Bootstrap and frozen XIDs have no parent */
if (!TransactionIdIsNormal(xid))
return InvalidTransactionId;
LWLockAcquire(SubTransCtl->ControlLock, LW_EXCLUSIVE);
ptr = (TransactionId *) SimpleLruReadPage(SubTransCtl, pageno, xid, false);
ptr += entryno;
parent = *ptr;
LWLockRelease(SubTransCtl->ControlLock);
return parent;
}
/*
* SubTransGetTopmostTransaction
*
* Returns the topmost transaction of the given transaction id.
*/
TransactionId
SubTransGetTopmostTransaction(TransactionId xid)
{
TransactionId parentXid = xid,
previousXid = xid;
while (TransactionIdIsValid(parentXid))
{
previousXid = parentXid;
parentXid = SubTransGetParent(parentXid);
}
Assert(TransactionIdIsValid(previousXid));
return previousXid;
}
/*
* SubTransXidsHaveCommonAncestor
*
* Returns true iff the Xids have a common ancestor
*/
bool
SubTransXidsHaveCommonAncestor(TransactionId xid1, TransactionId xid2)
{
if (TransactionIdEquals(xid1, xid2))
return true;
while (TransactionIdIsValid(xid1) && TransactionIdIsValid(xid2))
{
if (TransactionIdPrecedes(xid2, xid1))
xid1 = SubTransGetParent(xid1);
else
xid2 = SubTransGetParent(xid2);
if (TransactionIdEquals(xid1, xid2))
return true;
}
return false;
}
/*
* Initialization of shared memory for Subtrans
*/
int
SUBTRANSShmemSize(void)
{
return SimpleLruShmemSize();
}
void
SUBTRANSShmemInit(void)
{
SimpleLruInit(SubTransCtl, "SUBTRANS Ctl", "pg_subtrans");
SubTransCtl->PagePrecedes = SubTransPagePrecedes;
}
/*
* This func must be called ONCE on system install. It creates
* the initial SubTrans segment. (The SubTrans directory is assumed to
* have been created by initdb, and SubTransShmemInit must have been called
* already.)
*/
void
BootStrapSUBTRANS(void)
{
int slotno;
LWLockAcquire(SubTransCtl->ControlLock, LW_EXCLUSIVE);
/* Create and zero the first page of the commit log */
slotno = ZeroSUBTRANSPage(0, false);
/* Make sure it's written out */
SimpleLruWritePage(SubTransCtl, slotno, NULL);
/* Assert(SubTransCtl->page_status[slotno] == SLRU_PAGE_CLEAN); */
LWLockRelease(SubTransCtl->ControlLock);
}
/*
* Initialize (or reinitialize) a page of SubTrans to zeroes.
* If writeXlog is TRUE, also emit an XLOG record saying we did this.
*
* The page is not actually written, just set up in shared memory.
* The slot number of the new page is returned.
*
* Control lock must be held at entry, and will be held at exit.
*/
static int
ZeroSUBTRANSPage(int pageno, bool writeXlog)
{
int slotno = SimpleLruZeroPage(SubTransCtl, pageno);
if (writeXlog)
WriteZeroPageXlogRec(pageno);
return slotno;
}
/*
* This must be called ONCE during postmaster or standalone-backend startup,
* after StartupXLOG has initialized ShmemVariableCache->nextXid.
*/
void
StartupSUBTRANS(void)
{
/*
* Initialize our idea of the latest page number.
*/
SimpleLruSetLatestPage(SubTransCtl,
TransactionIdToPage(ShmemVariableCache->nextXid));
}
/*
* This must be called ONCE during postmaster or standalone-backend shutdown
*/
void
ShutdownSUBTRANS(void)
{
SimpleLruFlush(SubTransCtl, false);
}
/*
* Perform a checkpoint --- either during shutdown, or on-the-fly
*/
void
CheckPointSUBTRANS(void)
{
SimpleLruFlush(SubTransCtl, true);
}
/*
* Make sure that SubTrans has room for a newly-allocated XID.
*
* NB: this is called while holding XidGenLock. We want it to be very fast
* most of the time; even when it's not so fast, no actual I/O need happen
* unless we're forced to write out a dirty subtrans or xlog page to make room
* in shared memory.
*/
void
ExtendSUBTRANS(TransactionId newestXact)
{
int pageno;
/*
* No work except at first XID of a page. But beware: just after
* wraparound, the first XID of page zero is FirstNormalTransactionId.
*/
if (TransactionIdToEntry(newestXact) != 0 &&
!TransactionIdEquals(newestXact, FirstNormalTransactionId))
return;
pageno = TransactionIdToPage(newestXact);
LWLockAcquire(SubTransCtl->ControlLock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroSUBTRANSPage(pageno, true);
LWLockRelease(SubTransCtl->ControlLock);
}
/*
* Remove all SubTrans segments before the one holding the passed transaction ID
*
* When this is called, we know that the database logically contains no
* reference to transaction IDs older than oldestXact. However, we must
* not truncate the SubTrans until we have performed a checkpoint, to ensure
* that no such references remain on disk either; else a crash just after
* the truncation might leave us with a problem. Since SubTrans segments hold
* a large number of transactions, the opportunity to actually remove a
* segment is fairly rare, and so it seems best not to do the checkpoint
* unless we have confirmed that there is a removable segment. Therefore
* we issue the checkpoint command here, not in higher-level code as might
* seem cleaner.
*/
void
TruncateSUBTRANS(TransactionId oldestXact)
{
int cutoffPage;
/*
* The cutoff point is the start of the segment containing oldestXact.
* We pass the *page* containing oldestXact to SimpleLruTruncate.
*/
cutoffPage = TransactionIdToPage(oldestXact);
SimpleLruTruncate(SubTransCtl, cutoffPage);
}
/*
* Decide which of two SubTrans page numbers is "older" for truncation purposes.
*
* We need to use comparison of TransactionIds here in order to do the right
* thing with wraparound XID arithmetic. However, if we are asked about
* page number zero, we don't want to hand InvalidTransactionId to
* TransactionIdPrecedes: it'll get weird about permanent xact IDs. So,
* offset both xids by FirstNormalTransactionId to avoid that.
*/
static bool
SubTransPagePrecedes(int page1, int page2)
{
TransactionId xid1;
TransactionId xid2;
xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
xid1 += FirstNormalTransactionId;
xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
xid2 += FirstNormalTransactionId;
return TransactionIdPrecedes(xid1, xid2);
}
/*
* Write a ZEROPAGE xlog record
*
* Note: xlog record is marked as outside transaction control, since we
* want it to be redone whether the invoking transaction commits or not.
* (Besides which, this is normally done just before entering a transaction.)
*/
static void
WriteZeroPageXlogRec(int pageno)
{
XLogRecData rdata;
rdata.buffer = InvalidBuffer;
rdata.data = (char *) (&pageno);
rdata.len = sizeof(int);
rdata.next = NULL;
(void) XLogInsert(RM_SLRU_ID, SUBTRANS_ZEROPAGE | XLOG_NO_TRAN, &rdata);
}
/* Redo a ZEROPAGE action during WAL replay */
void
subtrans_zeropage_redo(int pageno)
{
int slotno;
LWLockAcquire(SubTransCtl->ControlLock, LW_EXCLUSIVE);
slotno = ZeroSUBTRANSPage(pageno, false);
SimpleLruWritePage(SubTransCtl, slotno, NULL);
/* Assert(SubTransCtl->page_status[slotno] == SLRU_PAGE_CLEAN); */
LWLockRelease(SubTransCtl->ControlLock);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/transam/transam.c,v 1.56 2003/11/29 19:51:40 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/transam.c,v 1.57 2004/07/01 00:49:42 tgl Exp $
*
* NOTES
* This file contains the high level access-method interface to the
@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/clog.h"
#include "access/subtrans.h"
#include "access/transam.h"
@ -35,44 +36,40 @@
bool AMI_OVERRIDE = false;
static bool TransactionLogTest(TransactionId transactionId, XidStatus status);
static XidStatus TransactionLogFetch(TransactionId transactionId);
static void TransactionLogUpdate(TransactionId transactionId,
XidStatus status);
/* ----------------
* Single-item cache for results of TransactionLogTest.
* Single-item cache for results of TransactionLogFetch.
* ----------------
*/
static TransactionId cachedTestXid = InvalidTransactionId;
static XidStatus cachedTestXidStatus;
static TransactionId cachedFetchXid = InvalidTransactionId;
static XidStatus cachedFetchXidStatus;
/* ----------------------------------------------------------------
* postgres log access method interface
*
* TransactionLogTest
* TransactionLogFetch
* TransactionLogUpdate
* ----------------------------------------------------------------
*/
/* --------------------------------
* TransactionLogTest
* --------------------------------
/*
* TransactionLogFetch --- fetch commit status of specified transaction id
*/
static bool /* true/false: does transaction id have
* specified status? */
TransactionLogTest(TransactionId transactionId, /* transaction id to test */
XidStatus status) /* transaction status */
static XidStatus
TransactionLogFetch(TransactionId transactionId)
{
XidStatus xidstatus; /* recorded status of xid */
XidStatus xidstatus;
/*
* Before going to the commit log manager, check our single item cache
* to see if we didn't just check the transaction status a moment ago.
*/
if (TransactionIdEquals(transactionId, cachedTestXid))
return (status == cachedTestXidStatus);
if (TransactionIdEquals(transactionId, cachedFetchXid))
return cachedFetchXidStatus;
/*
* Also, check to see if the transaction ID is a permanent one.
@ -80,10 +77,10 @@ TransactionLogTest(TransactionId transactionId, /* transaction id to test */
if (!TransactionIdIsNormal(transactionId))
{
if (TransactionIdEquals(transactionId, BootstrapTransactionId))
return (status == TRANSACTION_STATUS_COMMITTED);
return TRANSACTION_STATUS_COMMITTED;
if (TransactionIdEquals(transactionId, FrozenTransactionId))
return (status == TRANSACTION_STATUS_COMMITTED);
return (status == TRANSACTION_STATUS_ABORTED);
return TRANSACTION_STATUS_COMMITTED;
return TRANSACTION_STATUS_ABORTED;
}
/*
@ -92,15 +89,17 @@ TransactionLogTest(TransactionId transactionId, /* transaction id to test */
xidstatus = TransactionIdGetStatus(transactionId);
/*
* DO NOT cache status for unfinished transactions!
* DO NOT cache status for unfinished or sub-committed transactions!
* We only cache status that is guaranteed not to change.
*/
if (xidstatus != TRANSACTION_STATUS_IN_PROGRESS)
if (xidstatus != TRANSACTION_STATUS_IN_PROGRESS &&
xidstatus != TRANSACTION_STATUS_SUB_COMMITTED)
{
TransactionIdStore(transactionId, &cachedTestXid);
cachedTestXidStatus = xidstatus;
TransactionIdStore(transactionId, &cachedFetchXid);
cachedFetchXidStatus = xidstatus;
}
return (status == xidstatus);
return xidstatus;
}
/* --------------------------------
@ -115,12 +114,23 @@ TransactionLogUpdate(TransactionId transactionId, /* trans id to update */
* update the commit log
*/
TransactionIdSetStatus(transactionId, status);
}
/*
* update (invalidate) our single item TransactionLogTest cache.
*/
TransactionIdStore(transactionId, &cachedTestXid);
cachedTestXidStatus = status;
/*
* TransactionLogMultiUpdate
*
* Update multiple transaction identifiers to a given status.
* Don't depend on this being atomic; it's not.
*/
static void
TransactionLogMultiUpdate(int nxids, TransactionId *xids, XidStatus status)
{
int i;
Assert(nxids != 0);
for (i = 0; i < nxids; i++)
TransactionIdSetStatus(xids[i], status);
}
/* --------------------------------
@ -171,13 +181,38 @@ AmiTransactionOverride(bool flag)
bool /* true if given transaction committed */
TransactionIdDidCommit(TransactionId transactionId)
{
XidStatus xidstatus;
if (AMI_OVERRIDE)
{
Assert(transactionId == BootstrapTransactionId);
return true;
}
return TransactionLogTest(transactionId, TRANSACTION_STATUS_COMMITTED);
xidstatus = TransactionLogFetch(transactionId);
/*
* If it's marked committed, it's committed.
*/
if (xidstatus == TRANSACTION_STATUS_COMMITTED)
return true;
/*
* If it's marked subcommitted, we have to check the parent recursively.
*/
if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
{
TransactionId parentXid;
parentXid = SubTransGetParent(transactionId);
Assert(TransactionIdIsValid(parentXid));
return TransactionIdDidCommit(parentXid);
}
/*
* It's not committed.
*/
return false;
}
/*
@ -190,35 +225,49 @@ TransactionIdDidCommit(TransactionId transactionId)
bool /* true if given transaction aborted */
TransactionIdDidAbort(TransactionId transactionId)
{
XidStatus xidstatus;
if (AMI_OVERRIDE)
{
Assert(transactionId == BootstrapTransactionId);
return false;
}
return TransactionLogTest(transactionId, TRANSACTION_STATUS_ABORTED);
}
xidstatus = TransactionLogFetch(transactionId);
/*
* Now this func in shmem.c and gives quality answer by scanning
* PGPROC structures of all running backend. - vadim 11/26/96
*
* Old comments:
* true if given transaction has neither committed nor aborted
*/
#ifdef NOT_USED
bool
TransactionIdIsInProgress(TransactionId transactionId)
{
if (AMI_OVERRIDE)
/*
* If it's marked aborted, it's aborted.
*/
if (xidstatus == TRANSACTION_STATUS_ABORTED)
return true;
/*
* If it's marked subcommitted, we have to check the parent recursively.
*
* If we detect that the parent has aborted, update pg_clog to show the
* subtransaction as aborted. This is only needed when the parent
* crashed before either committing or aborting. We want to clean up
* pg_clog so future visitors don't need to make this check again.
*/
if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
{
Assert(transactionId == BootstrapTransactionId);
return false;
TransactionId parentXid;
bool parentAborted;
parentXid = SubTransGetParent(transactionId);
parentAborted = TransactionIdDidAbort(parentXid);
if (parentAborted)
TransactionIdAbort(transactionId);
return parentAborted;
}
return TransactionLogTest(transactionId, TRANSACTION_STATUS_IN_PROGRESS);
/*
* It's not aborted.
*/
return false;
}
#endif /* NOT_USED */
/* --------------------------------
* TransactionId Commit
@ -252,6 +301,46 @@ TransactionIdAbort(TransactionId transactionId)
TransactionLogUpdate(transactionId, TRANSACTION_STATUS_ABORTED);
}
/*
* TransactionIdSubCommit
* Marks the subtransaction associated with the identifier as
* sub-committed.
*/
void
TransactionIdSubCommit(TransactionId transactionId)
{
TransactionLogUpdate(transactionId, TRANSACTION_STATUS_SUB_COMMITTED);
}
/*
* TransactionIdCommitTree
* Marks all the given transaction ids as committed.
*
* The caller has to be sure that this is used only to mark subcommitted
* subtransactions as committed, and only *after* marking the toplevel
* parent as committed. Otherwise there is a race condition against
* TransactionIdDidCommit.
*/
void
TransactionIdCommitTree(int nxids, TransactionId *xids)
{
if (nxids > 0)
TransactionLogMultiUpdate(nxids, xids, TRANSACTION_STATUS_COMMITTED);
}
/*
* TransactionIdAbortTree
* Marks all the given transaction ids as aborted.
*
* We don't need to worry about the non-atomic behavior, since any onlookers
* will consider all the xacts as not-yet-committed anyway.
*/
void
TransactionIdAbortTree(int nxids, TransactionId *xids)
{
if (nxids > 0)
TransactionLogMultiUpdate(nxids, xids, TRANSACTION_STATUS_ABORTED);
}
/*
* TransactionIdPrecedes --- is id1 logically < id2?

View File

@ -6,7 +6,7 @@
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.55 2004/01/26 19:15:59 tgl Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.56 2004/07/01 00:49:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -14,6 +14,7 @@
#include "postgres.h"
#include "access/clog.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@ -30,7 +31,7 @@ VariableCache ShmemVariableCache = NULL;
* Allocate the next XID for my new transaction.
*/
TransactionId
GetNewTransactionId(void)
GetNewTransactionId(bool isSubXact)
{
TransactionId xid;
@ -52,8 +53,11 @@ GetNewTransactionId(void)
* commit a later XID before we zero the page. Fortunately, a page of
* the commit log holds 32K or more transactions, so we don't have to
* do this very often.
*
* Extend pg_subtrans too.
*/
ExtendCLOG(xid);
ExtendSUBTRANS(xid);
/*
* Now advance the nextXid counter. This must not happen until after
@ -82,8 +86,11 @@ GetNewTransactionId(void)
* its own spinlock used only for fetching/storing that PGPROC's xid.
* (SInvalLock would then mean primarily that PGPROCs couldn't be added/
* removed while holding the lock.)
*
* We don't want a subtransaction to update the stored Xid; we'll check
* if a transaction Xid is a running subxact by checking pg_subtrans.
*/
if (MyProc != NULL)
if (MyProc != NULL && !isSubXact)
MyProc->xid = xid;
LWLockRelease(XidGenLock);

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.146 2004/06/03 02:08:00 tgl Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.147 2004/07/01 00:49:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,6 +22,7 @@
#include <sys/time.h>
#include "access/clog.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlog.h"
@ -2755,6 +2756,7 @@ BootStrapXLOG(void)
/* Bootstrap the commit log, too */
BootStrapCLOG();
BootStrapSUBTRANS();
}
static char *
@ -3154,6 +3156,7 @@ StartupXLOG(void)
/* Start up the commit log, too */
StartupCLOG();
StartupSUBTRANS();
ereport(LOG,
(errmsg("database system is ready")));
@ -3292,6 +3295,7 @@ ShutdownXLOG(int code, Datum arg)
CritSectionCount++;
CreateCheckPoint(true, true);
ShutdownCLOG();
ShutdownSUBTRANS();
CritSectionCount--;
ereport(LOG,
@ -3467,6 +3471,7 @@ CreateCheckPoint(bool shutdown, bool force)
END_CRIT_SECTION();
CheckPointCLOG();
CheckPointSUBTRANS();
FlushBufferPool();
START_CRIT_SECTION();

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/async.c,v 1.112 2004/05/26 04:41:10 neilc Exp $
* $PostgreSQL: pgsql/src/backend/commands/async.c,v 1.113 2004/07/01 00:50:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -97,11 +97,17 @@
* State for outbound notifies consists of a list of all relnames NOTIFYed
* in the current transaction. We do not actually perform a NOTIFY until
* and unless the transaction commits. pendingNotifies is NIL if no
* NOTIFYs have been done in the current transaction. The List nodes and
* referenced strings are all palloc'd in TopTransactionContext.
* NOTIFYs have been done in the current transaction.
*
* The list is kept in CurTransactionContext. In subtransactions, each
* subtransaction has its own list in its own CurTransactionContext, but
* successful subtransactions attach their lists to their parent's list.
* Failed subtransactions simply discard their lists.
*/
static List *pendingNotifies = NIL;
static List *upperPendingNotifies = NIL; /* list of upper-xact lists */
/*
* State for inbound notifies consists of two flags: one saying whether
* the signal handler is currently allowed to call ProcessIncomingNotify
@ -155,11 +161,11 @@ Async_Notify(char *relname)
{
/*
* The name list needs to live until end of transaction, so store
* it in the top transaction context.
* it in the transaction context.
*/
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(TopTransactionContext);
oldcontext = MemoryContextSwitchTo(CurTransactionContext);
pendingNotifies = lcons(pstrdup(relname), pendingNotifies);
@ -606,6 +612,60 @@ AtAbort_Notify(void)
ClearPendingNotifies();
}
/*
* AtSubStart_Notify() --- Take care of subtransaction start.
*
* Push empty state for the new subtransaction.
*/
void
AtSubStart_Notify(void)
{
MemoryContext old_cxt;
/* Keep the list-of-lists in TopTransactionContext for simplicity */
old_cxt = MemoryContextSwitchTo(TopTransactionContext);
upperPendingNotifies = lcons(pendingNotifies, upperPendingNotifies);
pendingNotifies = NIL;
MemoryContextSwitchTo(old_cxt);
}
/*
* AtSubCommit_Notify() --- Take care of subtransaction commit.
*
* Reassign all items in the pending notifies list to the parent transaction.
*/
void
AtSubCommit_Notify(void)
{
List *parentPendingNotifies;
parentPendingNotifies = (List *) linitial(upperPendingNotifies);
upperPendingNotifies = list_delete_first(upperPendingNotifies);
/*
* We could try to eliminate duplicates here, but it seems not worthwhile.
*/
pendingNotifies = list_concat(parentPendingNotifies, pendingNotifies);
}
/*
* AtSubAbort_Notify() --- Take care of subtransaction abort.
*/
void
AtSubAbort_Notify(void)
{
/*
* All we have to do is pop the stack --- the notifies made in this
* subxact are no longer interesting, and the space will be freed when
* CurTransactionContext is recycled.
*/
pendingNotifies = (List *) linitial(upperPendingNotifies);
upperPendingNotifies = list_delete_first(upperPendingNotifies);
}
/*
*--------------------------------------------------------------
* NotifyInterruptHandler
@ -951,7 +1011,7 @@ ClearPendingNotifies(void)
/*
* We used to have to explicitly deallocate the list members and
* nodes, because they were malloc'd. Now, since we know they are
* palloc'd in TopTransactionContext, we need not do that --- they'll
* palloc'd in CurTransactionContext, we need not do that --- they'll
* go away automatically at transaction exit. We need only reset the
* list head pointer.
*/

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.117 2004/06/25 21:55:53 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.118 2004/07/01 00:50:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -76,8 +76,8 @@ typedef struct OnCommitItem
* entries in the list until commit so that we can roll back if
* needed.
*/
bool created_in_cur_xact;
bool deleted_in_cur_xact;
TransactionId creating_xid;
TransactionId deleting_xid;
} OnCommitItem;
static List *on_commits = NIL;
@ -5483,8 +5483,8 @@ register_on_commit_action(Oid relid, OnCommitAction action)
oc = (OnCommitItem *) palloc(sizeof(OnCommitItem));
oc->relid = relid;
oc->oncommit = action;
oc->created_in_cur_xact = true;
oc->deleted_in_cur_xact = false;
oc->creating_xid = GetCurrentTransactionId();
oc->deleting_xid = InvalidTransactionId;
on_commits = lcons(oc, on_commits);
@ -5507,7 +5507,7 @@ remove_on_commit_action(Oid relid)
if (oc->relid == relid)
{
oc->deleted_in_cur_xact = true;
oc->deleting_xid = GetCurrentTransactionId();
break;
}
}
@ -5522,6 +5522,7 @@ remove_on_commit_action(Oid relid)
void
PreCommit_on_commit_actions(void)
{
TransactionId xid = GetCurrentTransactionId();
ListCell *l;
foreach(l, on_commits)
@ -5529,7 +5530,7 @@ PreCommit_on_commit_actions(void)
OnCommitItem *oc = (OnCommitItem *) lfirst(l);
/* Ignore entry if already dropped in this xact */
if (oc->deleted_in_cur_xact)
if (oc->deleting_xid == xid)
continue;
switch (oc->oncommit)
@ -5556,7 +5557,7 @@ PreCommit_on_commit_actions(void)
* remove_on_commit_action, so the entry should get
* marked as deleted.
*/
Assert(oc->deleted_in_cur_xact);
Assert(oc->deleting_xid == xid);
break;
}
}
@ -5572,7 +5573,7 @@ PreCommit_on_commit_actions(void)
* during abort, remove those created during this transaction.
*/
void
AtEOXact_on_commit_actions(bool isCommit)
AtEOXact_on_commit_actions(bool isCommit, TransactionId xid)
{
ListCell *cur_item;
ListCell *prev_item;
@ -5584,8 +5585,8 @@ AtEOXact_on_commit_actions(bool isCommit)
{
OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
if (isCommit ? oc->deleted_in_cur_xact :
oc->created_in_cur_xact)
if (isCommit ? TransactionIdEquals(oc->deleting_xid, xid) :
TransactionIdEquals(oc->creating_xid, xid))
{
/* cur_item must be removed */
on_commits = list_delete_cell(on_commits, cur_item, prev_item);
@ -5598,8 +5599,52 @@ AtEOXact_on_commit_actions(bool isCommit)
else
{
/* cur_item must be preserved */
oc->deleted_in_cur_xact = false;
oc->created_in_cur_xact = false;
oc->creating_xid = InvalidTransactionId;
oc->deleting_xid = InvalidTransactionId;
prev_item = cur_item;
cur_item = lnext(prev_item);
}
}
}
/*
* Post-subcommit or post-subabort cleanup for ON COMMIT management.
*
* During subabort, we can immediately remove entries created during this
* subtransaction. During subcommit, just relabel entries marked during
* this subtransaction as being the parent's responsibility.
*/
void
AtEOSubXact_on_commit_actions(bool isCommit, TransactionId childXid,
TransactionId parentXid)
{
ListCell *cur_item;
ListCell *prev_item;
prev_item = NULL;
cur_item = list_head(on_commits);
while (cur_item != NULL)
{
OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
if (!isCommit && TransactionIdEquals(oc->creating_xid, childXid))
{
/* cur_item must be removed */
on_commits = list_delete_cell(on_commits, cur_item, prev_item);
pfree(oc);
if (prev_item)
cur_item = lnext(prev_item);
else
cur_item = list_head(on_commits);
}
else
{
/* cur_item must be preserved */
if (TransactionIdEquals(oc->creating_xid, childXid))
oc->creating_xid = parentXid;
if (TransactionIdEquals(oc->deleting_xid, childXid))
oc->deleting_xid = isCommit ? parentXid : InvalidTransactionId;
prev_item = cur_item;
cur_item = lnext(prev_item);
}

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.165 2004/05/26 04:41:12 neilc Exp $
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.166 2004/07/01 00:50:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -50,9 +50,6 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
MemoryContext per_tuple_context);
static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
MemoryContext per_tuple_context);
/*
@ -1639,47 +1636,130 @@ ltrmark:;
/* ----------
* Deferred trigger stuff
*
* The DeferredTriggersData struct holds data about pending deferred
* trigger events during the current transaction tree. The struct and
* most of its subsidiary data are kept in TopTransactionContext; however
* the individual event records are kept in CurTransactionContext, so that
* they will easily go away during subtransaction abort.
*
* DeferredTriggersData has the following fields:
*
* state keeps track of the deferred state of each trigger
* (including the global state). This is saved and restored across
* failed subtransactions.
*
* events is the head of the list of events.
*
* tail_thisxact points to the tail of the list, for the current
* transaction (whether main transaction or subtransaction). We always
* append to the list using this pointer.
*
* events_imm points to the last element scanned by the last
* deferredTriggerInvokeEvents call. We can use this to avoid rescanning
* unnecessarily; if it's NULL, the scan should start at the head of the
* list. Its name comes from the fact that it's set to the last event fired
* by the last call to immediate triggers.
*
* tail_stack and imm_stack are stacks of pointer, which hold the pointers
* to the tail and the "immediate" events as of the start of a subtransaction.
* We use to revert them when aborting the subtransaction.
*
* state_stack is a stack of pointers to saved copies of the deferred-trigger
* state data; each subtransaction level that modifies that state first
* saves a copy, which we use to restore the state if we abort.
*
* numpushed and numalloc keep control of allocation and storage in the above
* stacks. numpushed is essentially the current subtransaction nesting depth.
*
* XXX We need to be able to save the per-event data in a file if it grows too
* large.
* ----------
*/
/* Per-item data */
typedef struct DeferredTriggerEventItem
{
Oid dti_tgoid;
TransactionId dti_done_xid;
int32 dti_state;
} DeferredTriggerEventItem;
typedef struct DeferredTriggerEventData *DeferredTriggerEvent;
/* Per-event data */
typedef struct DeferredTriggerEventData
{
DeferredTriggerEvent dte_next; /* list link */
int32 dte_event;
Oid dte_relid;
TransactionId dte_done_xid;
ItemPointerData dte_oldctid;
ItemPointerData dte_newctid;
int32 dte_n_items;
/* dte_item is actually a variable-size array, of length dte_n_items */
DeferredTriggerEventItem dte_item[1];
} DeferredTriggerEventData;
/* Per-trigger status data */
typedef struct DeferredTriggerStatusData
{
Oid dts_tgoid;
bool dts_tgisdeferred;
} DeferredTriggerStatusData;
typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
/*
* Trigger deferral status data.
*
* We make this a single palloc'd object so it can be copied and freed easily.
*
* all_isset and all_isdeferred are used to keep track
* of SET CONSTRAINTS ALL {DEFERRED, IMMEDIATE}.
*
* trigstates[] stores per-trigger tgisdeferred settings.
*/
typedef struct DeferredTriggerStateData
{
bool all_isset;
bool all_isdeferred;
int numstates; /* number of trigstates[] entries in use */
int numalloc; /* allocated size of trigstates[] */
DeferredTriggerStatusData trigstates[1]; /* VARIABLE LENGTH ARRAY */
} DeferredTriggerStateData;
typedef DeferredTriggerStateData *DeferredTriggerState;
/* Per-transaction data */
typedef struct DeferredTriggersData
{
/* Internal data is held in a per-transaction memory context */
MemoryContext deftrig_cxt;
/* ALL DEFERRED or ALL IMMEDIATE */
bool deftrig_all_isset;
bool deftrig_all_isdeferred;
/* Per trigger state */
List *deftrig_trigstates;
/* List of pending deferred triggers. Previous comment below */
DeferredTriggerEvent deftrig_events;
DeferredTriggerEvent deftrig_events_imm;
DeferredTriggerEvent deftrig_event_tail;
DeferredTriggerState state;
DeferredTriggerEvent events;
DeferredTriggerEvent tail_thisxact;
DeferredTriggerEvent events_imm;
DeferredTriggerEvent *tail_stack;
DeferredTriggerEvent *imm_stack;
DeferredTriggerState *state_stack;
int numpushed;
int numalloc;
} DeferredTriggersData;
/* ----------
* deftrig_events, deftrig_event_tail:
* The list of pending deferred trigger events during the current transaction.
*
* deftrig_events is the head, deftrig_event_tail is the last entry.
* Because this can grow pretty large, we don't use separate List nodes,
* but instead thread the list through the dte_next fields of the member
* nodes. Saves just a few bytes per entry, but that adds up.
*
* deftrig_events_imm holds the tail pointer as of the last
* deferredTriggerInvokeEvents call; we can use this to avoid rescanning
* entries unnecessarily. It is NULL if deferredTriggerInvokeEvents
* hasn't run since the last state change.
*
* XXX Need to be able to shove this data out to a file if it grows too
* large...
* ----------
*/
typedef DeferredTriggersData *DeferredTriggers;
static DeferredTriggers deferredTriggers;
static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
MemoryContext per_tuple_context);
static DeferredTriggerState DeferredTriggerStateCreate(int numalloc);
static DeferredTriggerState DeferredTriggerStateCopy(DeferredTriggerState state);
static DeferredTriggerState DeferredTriggerStateAddItem(DeferredTriggerState state,
Oid tgoid, bool tgisdeferred);
/* ----------
* deferredTriggerCheckState()
*
@ -1690,13 +1770,12 @@ static DeferredTriggers deferredTriggers;
static bool
deferredTriggerCheckState(Oid tgoid, int32 itemstate)
{
MemoryContext oldcxt;
ListCell *sl;
DeferredTriggerStatus trigstate;
bool tgisdeferred;
int i;
/*
* Not deferrable triggers (i.e. normal AFTER ROW triggers and
* constraints declared NOT DEFERRABLE, the state is always false.
* For not-deferrable triggers (i.e. normal AFTER ROW triggers and
* constraints declared NOT DEFERRABLE), the state is always false.
*/
if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
return false;
@ -1704,37 +1783,29 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
/*
* Lookup if we know an individual state for this trigger
*/
foreach(sl, deferredTriggers->deftrig_trigstates)
for (i = 0; i < deferredTriggers->state->numstates; i++)
{
trigstate = (DeferredTriggerStatus) lfirst(sl);
if (trigstate->dts_tgoid == tgoid)
return trigstate->dts_tgisdeferred;
if (deferredTriggers->state->trigstates[i].dts_tgoid == tgoid)
return deferredTriggers->state->trigstates[i].dts_tgisdeferred;
}
/*
* No individual state known - so if the user issued a SET CONSTRAINT
* ALL ..., we return that instead of the triggers default state.
*/
if (deferredTriggers->deftrig_all_isset)
return deferredTriggers->deftrig_all_isdeferred;
if (deferredTriggers->state->all_isset)
return deferredTriggers->state->all_isdeferred;
/*
* No ALL state known either, remember the default state as the
* current and return that.
* current and return that. (XXX why do we bother making a state entry?)
*/
oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt);
tgisdeferred = ((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0);
deferredTriggers->state =
DeferredTriggerStateAddItem(deferredTriggers->state,
tgoid, tgisdeferred);
trigstate = (DeferredTriggerStatus)
palloc(sizeof(DeferredTriggerStatusData));
trigstate->dts_tgoid = tgoid;
trigstate->dts_tgisdeferred =
((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0);
deferredTriggers->deftrig_trigstates =
lappend(deferredTriggers->deftrig_trigstates, trigstate);
MemoryContextSwitchTo(oldcxt);
return trigstate->dts_tgisdeferred;
return tgisdeferred;
}
@ -1747,22 +1818,18 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
static void
deferredTriggerAddEvent(DeferredTriggerEvent event)
{
/*
* Since the event list could grow quite long, we keep track of the
* list tail and append there, rather than just doing a stupid
* "lappend". This avoids O(N^2) behavior for large numbers of events.
*/
event->dte_next = NULL;
if (deferredTriggers->deftrig_event_tail == NULL)
Assert(event->dte_next == NULL);
if (deferredTriggers->tail_thisxact == NULL)
{
/* first list entry */
deferredTriggers->deftrig_events = event;
deferredTriggers->deftrig_event_tail = event;
deferredTriggers->events = event;
deferredTriggers->tail_thisxact = event;
}
else
{
deferredTriggers->deftrig_event_tail->dte_next = event;
deferredTriggers->deftrig_event_tail = event;
deferredTriggers->tail_thisxact->dte_next = event;
deferredTriggers->tail_thisxact = event;
}
}
@ -1915,18 +1982,18 @@ deferredTriggerInvokeEvents(bool immediate_only)
/*
* If immediate_only is true, then the only events that could need
* firing are those since deftrig_events_imm. (But if
* deftrig_events_imm is NULL, we must scan the entire list.)
* firing are those since events_imm. (But if
* events_imm is NULL, we must scan the entire list.)
*/
if (immediate_only && deferredTriggers->deftrig_events_imm != NULL)
if (immediate_only && deferredTriggers->events_imm != NULL)
{
prev_event = deferredTriggers->deftrig_events_imm;
prev_event = deferredTriggers->events_imm;
event = prev_event->dte_next;
}
else
{
prev_event = NULL;
event = deferredTriggers->deftrig_events;
event = deferredTriggers->events;
}
while (event != NULL)
@ -1936,10 +2003,13 @@ deferredTriggerInvokeEvents(bool immediate_only)
int i;
/*
* Check if event is already completely done.
* Skip executing cancelled events, and events done by transactions
* that are not aborted.
*/
if (!(event->dte_event & (TRIGGER_DEFERRED_DONE |
TRIGGER_DEFERRED_CANCELED)))
if (!(event->dte_event & TRIGGER_DEFERRED_CANCELED) ||
(event->dte_event & TRIGGER_DEFERRED_DONE &&
TransactionIdIsValid(event->dte_done_xid) &&
!TransactionIdDidAbort(event->dte_done_xid)))
{
MemoryContextReset(per_tuple_context);
@ -1948,7 +2018,9 @@ deferredTriggerInvokeEvents(bool immediate_only)
*/
for (i = 0; i < event->dte_n_items; i++)
{
if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE)
if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE &&
TransactionIdIsValid(event->dte_item[i].dti_done_xid) &&
!(TransactionIdDidAbort(event->dte_item[i].dti_done_xid)))
continue;
/*
@ -2003,6 +2075,7 @@ deferredTriggerInvokeEvents(bool immediate_only)
per_tuple_context);
event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
event->dte_item[i].dti_done_xid = GetCurrentTransactionId();
} /* end loop over items within event */
}
@ -2022,23 +2095,27 @@ deferredTriggerInvokeEvents(bool immediate_only)
}
else
{
/* Done */
if (immediate_only)
/*
* We can drop an item if it's done, but only if we're not
* inside a subtransaction because it could abort later on.
* We will want to check the item again if it does.
*/
if (immediate_only && !IsSubTransaction())
{
/* delink it from list and free it */
if (prev_event)
prev_event->dte_next = next_event;
else
deferredTriggers->deftrig_events = next_event;
deferredTriggers->events = next_event;
pfree(event);
}
else
{
/*
* We will clean up later, but just for paranoia's sake,
* mark the event done.
* Mark the event done.
*/
event->dte_event |= TRIGGER_DEFERRED_DONE;
event->dte_done_xid = GetCurrentTransactionId();
}
}
@ -2046,10 +2123,10 @@ deferredTriggerInvokeEvents(bool immediate_only)
}
/* Update list tail pointer in case we just deleted tail event */
deferredTriggers->deftrig_event_tail = prev_event;
deferredTriggers->tail_thisxact = prev_event;
/* Set the immediate event pointer for next time */
deferredTriggers->deftrig_events_imm = prev_event;
deferredTriggers->events_imm = prev_event;
/* Release working resources */
if (rel)
@ -2060,23 +2137,6 @@ deferredTriggerInvokeEvents(bool immediate_only)
MemoryContextDelete(per_tuple_context);
}
/* ----------
* DeferredTriggerInit()
*
* Initialize the deferred trigger mechanism. This is called during
* backend startup and is guaranteed to be before the first of all
* transactions.
* ----------
*/
void
DeferredTriggerInit(void)
{
/* Nothing to do */
;
}
/* ----------
* DeferredTriggerBeginXact()
*
@ -2087,34 +2147,24 @@ DeferredTriggerInit(void)
void
DeferredTriggerBeginXact(void)
{
/*
* This will be changed to a special context when the nested
* transactions project moves forward.
*/
MemoryContext cxt = TopTransactionContext;
Assert(deferredTriggers == NULL);
deferredTriggers = (DeferredTriggers) MemoryContextAlloc(TopTransactionContext,
sizeof(DeferredTriggersData));
/*
* Create the per transaction memory context
*/
deferredTriggers->deftrig_cxt = AllocSetContextCreate(cxt,
"DeferredTriggerXact",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
deferredTriggers = (DeferredTriggers)
MemoryContextAlloc(TopTransactionContext,
sizeof(DeferredTriggersData));
/*
* If unspecified, constraints default to IMMEDIATE, per SQL
*/
deferredTriggers->deftrig_all_isdeferred = false;
deferredTriggers->deftrig_all_isset = false;
deferredTriggers->deftrig_trigstates = NIL;
deferredTriggers->deftrig_events = NULL;
deferredTriggers->deftrig_events_imm = NULL;
deferredTriggers->deftrig_event_tail = NULL;
deferredTriggers->state = DeferredTriggerStateCreate(8);
deferredTriggers->events = NULL;
deferredTriggers->events_imm = NULL;
deferredTriggers->tail_thisxact = NULL;
deferredTriggers->tail_stack = NULL;
deferredTriggers->imm_stack = NULL;
deferredTriggers->state_stack = NULL;
deferredTriggers->numalloc = 0;
deferredTriggers->numpushed = 0;
}
@ -2156,6 +2206,12 @@ DeferredTriggerEndXact(void)
deferredTriggerInvokeEvents(false);
/*
* Forget everything we know about deferred triggers.
*
* Since all the info is in TopTransactionContext or children thereof,
* we need do nothing special to reclaim memory.
*/
deferredTriggers = NULL;
}
@ -2179,10 +2235,217 @@ DeferredTriggerAbortXact(void)
/*
* Forget everything we know about deferred triggers.
*
* Since all the info is in TopTransactionContext or children thereof,
* we need do nothing special to reclaim memory.
*/
deferredTriggers = NULL;
}
/*
* DeferredTriggerBeginSubXact()
*
* Start a subtransaction.
*/
void
DeferredTriggerBeginSubXact(void)
{
/*
* Ignore call if the transaction is in aborted state.
*/
if (deferredTriggers == NULL)
return;
/*
* Allocate more space in the stacks if needed.
*/
if (deferredTriggers->numpushed == deferredTriggers->numalloc)
{
if (deferredTriggers->numalloc == 0)
{
MemoryContext old_cxt;
old_cxt = MemoryContextSwitchTo(TopTransactionContext);
#define DEFTRIG_INITALLOC 8
deferredTriggers->tail_stack = (DeferredTriggerEvent *)
palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent));
deferredTriggers->imm_stack = (DeferredTriggerEvent *)
palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent));
deferredTriggers->state_stack = (DeferredTriggerState *)
palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerState));
deferredTriggers->numalloc = DEFTRIG_INITALLOC;
MemoryContextSwitchTo(old_cxt);
}
else
{
/* repalloc will keep the stacks in the same context */
deferredTriggers->numalloc *= 2;
deferredTriggers->tail_stack = (DeferredTriggerEvent *)
repalloc(deferredTriggers->tail_stack,
deferredTriggers->numalloc * sizeof(DeferredTriggerEvent));
deferredTriggers->imm_stack = (DeferredTriggerEvent *)
repalloc(deferredTriggers->imm_stack,
deferredTriggers->numalloc * sizeof(DeferredTriggerEvent));
deferredTriggers->state_stack = (DeferredTriggerState *)
repalloc(deferredTriggers->state_stack,
deferredTriggers->numalloc * sizeof(DeferredTriggerState));
}
}
/*
* Push the current list position into the stack and reset the
* pointer.
*/
deferredTriggers->tail_stack[deferredTriggers->numpushed] =
deferredTriggers->tail_thisxact;
deferredTriggers->imm_stack[deferredTriggers->numpushed] =
deferredTriggers->events_imm;
/* State is not saved until/unless changed */
deferredTriggers->state_stack[deferredTriggers->numpushed] = NULL;
deferredTriggers->numpushed++;
}
/*
* DeferredTriggerEndSubXact()
*
* The current subtransaction is ending.
*/
void
DeferredTriggerEndSubXact(bool isCommit)
{
DeferredTriggerState state;
/*
* Ignore call if the transaction is in aborted state.
*/
if (deferredTriggers == NULL)
return;
/*
* Move back the "top of the stack."
*/
Assert(deferredTriggers->numpushed > 0);
deferredTriggers->numpushed--;
if (isCommit)
{
/* If we saved a prior state, we don't need it anymore */
state = deferredTriggers->state_stack[deferredTriggers->numpushed];
if (state != NULL)
pfree(state);
}
else
{
/*
* Aborting --- restore the pointers from the stacks.
*/
deferredTriggers->tail_thisxact =
deferredTriggers->tail_stack[deferredTriggers->numpushed];
deferredTriggers->events_imm =
deferredTriggers->imm_stack[deferredTriggers->numpushed];
/*
* Cleanup the head and the tail of the list.
*/
if (deferredTriggers->tail_thisxact == NULL)
deferredTriggers->events = NULL;
else
deferredTriggers->tail_thisxact->dte_next = NULL;
/*
* We don't need to free the items, since the CurTransactionContext
* will be reset shortly.
*/
/*
* Restore the trigger state. If the saved state is NULL, then
* this subxact didn't save it, so it doesn't need restoring.
*/
state = deferredTriggers->state_stack[deferredTriggers->numpushed];
if (state != NULL)
{
pfree(deferredTriggers->state);
deferredTriggers->state = state;
}
}
}
/*
* Create an empty DeferredTriggerState with room for numalloc trigstates
*/
static DeferredTriggerState
DeferredTriggerStateCreate(int numalloc)
{
DeferredTriggerState state;
/* Behave sanely with numalloc == 0 */
if (numalloc <= 0)
numalloc = 1;
/*
* We assume that zeroing will correctly initialize the state values.
*/
state = (DeferredTriggerState)
MemoryContextAllocZero(TopTransactionContext,
sizeof(DeferredTriggerStateData) +
(numalloc - 1) * sizeof(DeferredTriggerStatusData));
state->numalloc = numalloc;
return state;
}
/*
* Copy a DeferredTriggerState
*/
static DeferredTriggerState
DeferredTriggerStateCopy(DeferredTriggerState origstate)
{
DeferredTriggerState state;
state = DeferredTriggerStateCreate(origstate->numstates);
state->all_isset = origstate->all_isset;
state->all_isdeferred = origstate->all_isdeferred;
state->numstates = origstate->numstates;
memcpy(state->trigstates, origstate->trigstates,
origstate->numstates * sizeof(DeferredTriggerStatusData));
return state;
}
/*
* Add a per-trigger item to a DeferredTriggerState. Returns possibly-changed
* pointer to the state object (it will change if we have to repalloc).
*/
static DeferredTriggerState
DeferredTriggerStateAddItem(DeferredTriggerState state,
Oid tgoid, bool tgisdeferred)
{
if (state->numstates >= state->numalloc)
{
int newalloc = state->numalloc * 2;
newalloc = Max(newalloc, 8); /* in case original has size 0 */
state = (DeferredTriggerState)
repalloc(state,
sizeof(DeferredTriggerStateData) +
(newalloc - 1) * sizeof(DeferredTriggerStatusData));
state->numalloc = newalloc;
Assert(state->numstates < state->numalloc);
}
state->trigstates[state->numstates].dts_tgoid = tgoid;
state->trigstates[state->numstates].dts_tgisdeferred = tgisdeferred;
state->numstates++;
return state;
}
/* ----------
* DeferredTriggerSetState()
@ -2193,14 +2456,23 @@ DeferredTriggerAbortXact(void)
void
DeferredTriggerSetState(ConstraintsSetStmt *stmt)
{
ListCell *l;
/*
* Ignore call if we aren't in a transaction.
*/
if (deferredTriggers == NULL)
return;
/*
* If in a subtransaction, and we didn't save the current state already,
* save it so it can be restored if the subtransaction aborts.
*/
if (deferredTriggers->numpushed > 0 &&
deferredTriggers->state_stack[deferredTriggers->numpushed - 1] == NULL)
{
deferredTriggers->state_stack[deferredTriggers->numpushed - 1] =
DeferredTriggerStateCopy(deferredTriggers->state);
}
/*
* Handle SET CONSTRAINTS ALL ...
*/
@ -2210,23 +2482,19 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
* Drop all per-transaction information about individual trigger
* states.
*/
list_free_deep(deferredTriggers->deftrig_trigstates);
deferredTriggers->deftrig_trigstates = NIL;
deferredTriggers->state->numstates = 0;
/*
* Set the per-transaction ALL state to known.
*/
deferredTriggers->deftrig_all_isset = true;
deferredTriggers->deftrig_all_isdeferred = stmt->deferred;
deferredTriggers->state->all_isset = true;
deferredTriggers->state->all_isdeferred = stmt->deferred;
}
else
{
Relation tgrel;
MemoryContext oldcxt;
bool found;
DeferredTriggerStatus state;
ListCell *ls;
List *loid = NIL;
ListCell *l;
List *oidlist = NIL;
/* ----------
* Handle SET CONSTRAINTS constraint-name [, ...]
@ -2241,6 +2509,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
ScanKeyData skey;
SysScanDesc tgscan;
HeapTuple htup;
bool found;
/*
* Check that only named constraints are set explicitly
@ -2285,7 +2554,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
cname)));
constr_oid = HeapTupleGetOid(htup);
loid = lappend_oid(loid, constr_oid);
oidlist = lappend_oid(oidlist, constr_oid);
found = true;
}
@ -2305,34 +2574,28 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
* Inside of a transaction block set the trigger states of
* individual triggers on transaction level.
*/
oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt);
foreach(l, loid)
foreach(l, oidlist)
{
found = false;
foreach(ls, deferredTriggers->deftrig_trigstates)
Oid tgoid = lfirst_oid(l);
bool found = false;
int i;
for (i = 0; i < deferredTriggers->state->numstates; i++)
{
state = (DeferredTriggerStatus) lfirst(ls);
if (state->dts_tgoid == lfirst_oid(l))
if (deferredTriggers->state->trigstates[i].dts_tgoid == tgoid)
{
state->dts_tgisdeferred = stmt->deferred;
deferredTriggers->state->trigstates[i].dts_tgisdeferred = stmt->deferred;
found = true;
break;
}
}
if (!found)
{
state = (DeferredTriggerStatus)
palloc(sizeof(DeferredTriggerStatusData));
state->dts_tgoid = lfirst_oid(l);
state->dts_tgisdeferred = stmt->deferred;
deferredTriggers->deftrig_trigstates =
lappend(deferredTriggers->deftrig_trigstates, state);
deferredTriggers->state =
DeferredTriggerStateAddItem(deferredTriggers->state,
tgoid, stmt->deferred);
}
}
MemoryContextSwitchTo(oldcxt);
}
/*
@ -2347,14 +2610,14 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
* entire list, in case some deferred events are now immediately
* invokable.
*/
deferredTriggers->deftrig_events_imm = NULL;
deferredTriggers->events_imm = NULL;
}
/* ----------
* DeferredTriggerSaveEvent()
*
* Called by ExecAR...Triggers() to add the event to the queue.
* Called by ExecA[RS]...Triggers() to add the event to the queue.
*
* NOTE: should be called only if we've determined that an event must
* be added to the queue.
@ -2423,9 +2686,10 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
return;
/*
* Create a new event
* Create a new event. We use the CurTransactionContext so the event
* will automatically go away if the subtransaction aborts.
*/
oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt);
oldcxt = MemoryContextSwitchTo(CurTransactionContext);
new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
n_enabled_triggers * sizeof(DeferredTriggerEventItem);
@ -2433,6 +2697,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
new_event = (DeferredTriggerEvent) palloc(new_size);
new_event->dte_next = NULL;
new_event->dte_event = event & TRIGGER_EVENT_OPMASK;
new_event->dte_done_xid = InvalidTransactionId;
if (row_trigger)
new_event->dte_event |= TRIGGER_EVENT_ROW;
new_event->dte_relid = rel->rd_id;
@ -2449,6 +2714,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
ev_item = &(new_event->dte_item[i]);
ev_item->dti_tgoid = trigger->tgoid;
ev_item->dti_done_xid = InvalidTransactionId;
ev_item->dti_state =
((trigger->tgdeferrable) ?
TRIGGER_DEFERRED_DEFERRABLE : 0) |
@ -2517,6 +2783,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
* the trigger at all.
*/
new_event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
new_event->dte_item[i].dti_done_xid = GetCurrentTransactionId();
}
}

View File

@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.281 2004/06/08 13:59:36 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.282 2004/07/01 00:50:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -25,6 +25,7 @@
#include "access/clog.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/subtrans.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/catname.h"
@ -798,8 +799,9 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID)
return;
}
/* Truncate CLOG to the oldest vacuumxid */
/* Truncate CLOG and SUBTRANS to the oldest vacuumxid */
TruncateCLOG(vacuumXID);
TruncateSUBTRANS(vacuumXID);
/* Give warning about impending wraparound problems */
if (frozenAlreadyWrapped)

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.97 2004/05/26 04:41:13 neilc Exp $
* $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.98 2004/07/01 00:50:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -470,10 +470,17 @@ show_timezone(void)
const char *
assign_XactIsoLevel(const char *value, bool doit, GucSource source)
{
if (doit && source >= PGC_S_INTERACTIVE && SerializableSnapshot != NULL)
if (doit && source >= PGC_S_INTERACTIVE)
{
if (SerializableSnapshot != NULL)
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query")));
if (IsSubTransaction())
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction")));
}
if (strcmp(value, "serializable") == 0)
{

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.118 2004/06/11 01:08:43 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.119 2004/07/01 00:50:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -29,6 +29,7 @@ int SPI_result;
static _SPI_connection *_SPI_stack = NULL;
static _SPI_connection *_SPI_current = NULL;
static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
static int _SPI_connected = -1;
static int _SPI_curid = -1;
@ -59,7 +60,7 @@ static bool _SPI_checktuples(void);
int
SPI_connect(void)
{
_SPI_connection *new_SPI_stack;
int newdepth;
/*
* When procedure called by Executor _SPI_curid expected to be equal
@ -70,39 +71,46 @@ SPI_connect(void)
if (_SPI_stack == NULL)
{
if (_SPI_connected != -1)
if (_SPI_connected != -1 || _SPI_stack_depth != 0)
elog(ERROR, "SPI stack corrupted");
new_SPI_stack = (_SPI_connection *) malloc(sizeof(_SPI_connection));
newdepth = 16;
_SPI_stack = (_SPI_connection *)
MemoryContextAlloc(TopTransactionContext,
newdepth * sizeof(_SPI_connection));
_SPI_stack_depth = newdepth;
}
else
{
if (_SPI_connected < 0)
if (_SPI_stack_depth <= 0 || _SPI_stack_depth <= _SPI_connected)
elog(ERROR, "SPI stack corrupted");
new_SPI_stack = (_SPI_connection *) realloc(_SPI_stack,
(_SPI_connected + 2) * sizeof(_SPI_connection));
if (_SPI_stack_depth == _SPI_connected + 1)
{
newdepth = _SPI_stack_depth * 2;
_SPI_stack = (_SPI_connection *)
repalloc(_SPI_stack,
newdepth * sizeof(_SPI_connection));
_SPI_stack_depth = newdepth;
}
}
if (new_SPI_stack == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
/*
* We' returning to procedure where _SPI_curid == _SPI_connected - 1
* We're entering procedure where _SPI_curid == _SPI_connected - 1
*/
_SPI_stack = new_SPI_stack;
_SPI_connected++;
Assert(_SPI_connected >= 0 && _SPI_connected < _SPI_stack_depth);
_SPI_current = &(_SPI_stack[_SPI_connected]);
_SPI_current->processed = 0;
_SPI_current->tuptable = NULL;
_SPI_current->connectXid = GetCurrentTransactionId();
/*
* Create memory contexts for this procedure
*
* XXX it would be better to use PortalContext as the parent context, but
* we may not be inside a portal (consider deferred-trigger
* execution).
* XXX it would be better to use PortalContext as the parent context,
* but we may not be inside a portal (consider deferred-trigger
* execution). Perhaps CurTransactionContext would do? For now it
* doesn't matter because we clean up explicitly in AtEOSubXact_SPI().
*/
_SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext,
"SPI Proc",
@ -152,28 +160,11 @@ SPI_finish(void)
_SPI_connected--;
_SPI_curid--;
if (_SPI_connected == -1)
{
free(_SPI_stack);
_SPI_stack = NULL;
_SPI_current = NULL;
}
else
{
_SPI_connection *new_SPI_stack;
new_SPI_stack = (_SPI_connection *) realloc(_SPI_stack,
(_SPI_connected + 1) * sizeof(_SPI_connection));
/* This could only fail with a pretty stupid malloc package ... */
if (new_SPI_stack == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
_SPI_stack = new_SPI_stack;
_SPI_current = &(_SPI_stack[_SPI_connected]);
}
return SPI_OK_FINISH;
}
/*
@ -187,23 +178,54 @@ AtEOXact_SPI(bool isCommit)
* freed automatically, so we can ignore them here. We just need to
* restore our static variables to initial state.
*/
if (_SPI_stack != NULL)
{
free(_SPI_stack);
if (isCommit)
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("freeing non-empty SPI stack"),
errhint("Check for missing \"SPI_finish\" calls")));
}
if (isCommit && _SPI_connected != -1)
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("transaction left non-empty SPI stack"),
errhint("Check for missing \"SPI_finish\" calls")));
_SPI_current = _SPI_stack = NULL;
_SPI_stack_depth = 0;
_SPI_connected = _SPI_curid = -1;
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
}
/*
* Clean up SPI state at subtransaction commit or abort.
*
* During commit, there shouldn't be any unclosed entries remaining from
* the current transaction; we throw them away if found.
*/
void
AtEOSubXact_SPI(bool isCommit, TransactionId childXid)
{
bool found = false;
while (_SPI_connected >= 0)
{
_SPI_connection *connection = &(_SPI_stack[_SPI_connected]);
int res;
if (connection->connectXid != childXid)
break; /* couldn't be any underneath it either */
found = true;
_SPI_curid = _SPI_connected - 1; /* avoid begin_call error */
res = SPI_finish();
Assert(res == SPI_OK_FINISH);
}
if (found && isCommit)
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("subtransaction left non-empty SPI stack"),
errhint("Check for missing \"SPI_finish\" calls")));
}
/* Pushes SPI stack to allow recursive SPI calls */
void
SPI_push(void)
@ -1148,16 +1170,18 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
res = SPI_ERROR_CURSOR;
goto fail;
}
else if (IsA(queryTree->utilityStmt, TransactionStmt))
{
res = SPI_ERROR_TRANSACTION;
goto fail;
}
res = SPI_OK_UTILITY;
if (plan == NULL)
{
ProcessUtility(queryTree->utilityStmt, dest, NULL);
CommandCounterIncrement();
if (IsA(queryTree->utilityStmt, TransactionStmt))
{
CommitTransactionCommand();
StartTransactionCommand();
}
else
CommandCounterIncrement();
}
}
else if (plan == NULL)
@ -1273,7 +1297,14 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
{
ProcessUtility(queryTree->utilityStmt, dest, NULL);
res = SPI_OK_UTILITY;
CommandCounterIncrement();
if (IsA(queryTree->utilityStmt, TransactionStmt))
{
CommitTransactionCommand();
StartTransactionCommand();
}
else
CommandCounterIncrement();
}
else
{

View File

@ -13,7 +13,7 @@
*
* Copyright (c) 2001-2003, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.76 2004/06/26 16:32:02 tgl Exp $
* $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.77 2004/07/01 00:50:36 tgl Exp $
* ----------
*/
#include "postgres.h"
@ -167,6 +167,7 @@ static void pgstat_write_statsfile(void);
static void pgstat_read_statsfile(HTAB **dbhash, Oid onlydb,
PgStat_StatBeEntry **betab,
int *numbackends);
static void backend_read_statsfile(void);
static void pgstat_setheader(PgStat_MsgHdr *hdr, int mtype);
static void pgstat_send(void *msg, int len);
@ -786,12 +787,7 @@ pgstat_vacuum_tabstat(void)
* If not done for this transaction, read the statistics collector
* stats file into some hash tables.
*/
if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
{
pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
&pgStatBeTable, &pgStatNumBackends);
pgStatDBHashXact = GetCurrentTransactionId();
}
backend_read_statsfile();
/*
* Lookup our own database entry
@ -1210,15 +1206,9 @@ pgstat_fetch_stat_dbentry(Oid dbid)
/*
* If not done for this transaction, read the statistics collector
* stats file into some hash tables. Be careful with the
* read_statsfile() call below!
* stats file into some hash tables.
*/
if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
{
pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
&pgStatBeTable, &pgStatNumBackends);
pgStatDBHashXact = GetCurrentTransactionId();
}
backend_read_statsfile();
/*
* Lookup the requested database
@ -1250,15 +1240,9 @@ pgstat_fetch_stat_tabentry(Oid relid)
/*
* If not done for this transaction, read the statistics collector
* stats file into some hash tables. Be careful with the
* read_statsfile() call below!
* stats file into some hash tables.
*/
if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
{
pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
&pgStatBeTable, &pgStatNumBackends);
pgStatDBHashXact = GetCurrentTransactionId();
}
backend_read_statsfile();
/*
* Lookup our database.
@ -1296,12 +1280,7 @@ pgstat_fetch_stat_tabentry(Oid relid)
PgStat_StatBeEntry *
pgstat_fetch_stat_beentry(int beid)
{
if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
{
pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
&pgStatBeTable, &pgStatNumBackends);
pgStatDBHashXact = GetCurrentTransactionId();
}
backend_read_statsfile();
if (beid < 1 || beid > pgStatNumBackends)
return NULL;
@ -1320,12 +1299,7 @@ pgstat_fetch_stat_beentry(int beid)
int
pgstat_fetch_stat_numbackends(void)
{
if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
{
pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
&pgStatBeTable, &pgStatNumBackends);
pgStatDBHashXact = GetCurrentTransactionId();
}
backend_read_statsfile();
return pgStatNumBackends;
}
@ -2759,11 +2733,32 @@ pgstat_read_statsfile(HTAB **dbhash, Oid onlydb,
fclose(fpin);
}
/*
* If not done for this transaction, read the statistics collector
* stats file into some hash tables.
*
* Because we store the hash tables in TopTransactionContext, the result
* is good for the entire current main transaction.
*/
static void
backend_read_statsfile(void)
{
TransactionId topXid = GetTopTransactionId();
if (!TransactionIdEquals(pgStatDBHashXact, topXid))
{
Assert(!pgStatRunningInCollector);
pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
&pgStatBeTable, &pgStatNumBackends);
pgStatDBHashXact = topXid;
}
}
/* ----------
* pgstat_recv_bestart() -
*
* Process a backend starup message.
* Process a backend startup message.
* ----------
*/
static void

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/buffer/bufmgr.c,v 1.171 2004/06/18 06:13:33 tgl Exp $
* $PostgreSQL: pgsql/src/backend/storage/buffer/bufmgr.c,v 1.172 2004/07/01 00:50:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -45,6 +45,7 @@
#include "storage/bufpage.h"
#include "storage/proc.h"
#include "storage/smgr.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
#include "pgstat.h"
@ -64,9 +65,13 @@ long NDirectFileRead; /* some I/O's are direct file access.
* bypass bufmgr */
long NDirectFileWrite; /* e.g., I/O in psort and hashjoin. */
/* List of upper-level-transaction buffer refcount arrays */
static List *upperRefCounts = NIL;
static void PinBuffer(BufferDesc *buf);
static void UnpinBuffer(BufferDesc *buf);
static void BufferFixLeak(Buffer bufnum, int32 shouldBe, bool emitWarning);
static void WaitIO(BufferDesc *buf);
static void StartBufferIO(BufferDesc *buf, bool forInput);
static void TerminateBufferIO(BufferDesc *buf, int err_flag);
@ -826,30 +831,104 @@ AtEOXact_Buffers(bool isCommit)
for (i = 0; i < NBuffers; i++)
{
if (PrivateRefCount[i] != 0)
{
BufferDesc *buf = &(BufferDescriptors[i]);
if (isCommit)
elog(WARNING,
"buffer refcount leak: [%03d] "
"(rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d)",
i,
buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
buf->tag.rnode.relNode,
buf->tag.blockNum, buf->flags,
buf->refcount, PrivateRefCount[i]);
PrivateRefCount[i] = 1; /* make sure we release shared pin */
LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
UnpinBuffer(buf);
LWLockRelease(BufMgrLock);
Assert(PrivateRefCount[i] == 0);
}
BufferFixLeak(i, 0, isCommit);
}
AtEOXact_LocalBuffers(isCommit);
}
/*
* During subtransaction start, save buffer reference counts.
*/
void
AtSubStart_Buffers(void)
{
int32 *copyRefCounts;
Size rcSize;
MemoryContext old_cxt;
/* this is probably the active context already, but be safe */
old_cxt = MemoryContextSwitchTo(CurTransactionContext);
/*
* We need to copy the current state of PrivateRefCount[]. In the typical
* scenario, few if any of the entries will be nonzero, and we could save
* space by storing only the nonzero ones. However, copying the whole
* thing is lots simpler and faster both here and in AtEOSubXact_Buffers,
* so it seems best to waste the space.
*/
rcSize = NBuffers * sizeof(int32);
copyRefCounts = (int32 *) palloc(rcSize);
memcpy(copyRefCounts, PrivateRefCount, rcSize);
/* Attach to list */
upperRefCounts = lcons(copyRefCounts, upperRefCounts);
MemoryContextSwitchTo(old_cxt);
}
/*
* AtEOSubXact_Buffers
*
* At subtransaction end, we restore the saved counts. If committing, we
* complain if the refcounts don't match; if aborting, just restore silently.
*/
void
AtEOSubXact_Buffers(bool isCommit)
{
int32 *oldRefCounts;
int i;
oldRefCounts = (int32 *) linitial(upperRefCounts);
upperRefCounts = list_delete_first(upperRefCounts);
for (i = 0; i < NBuffers; i++)
{
if (PrivateRefCount[i] != oldRefCounts[i])
BufferFixLeak(i, oldRefCounts[i], isCommit);
}
pfree(oldRefCounts);
}
/*
* Fix a buffer refcount leak.
*
* The caller does not hold the BufMgrLock.
*/
static void
BufferFixLeak(Buffer bufnum, int32 shouldBe, bool emitWarning)
{
BufferDesc *buf = &(BufferDescriptors[bufnum]);
if (emitWarning)
elog(WARNING,
"buffer refcount leak: [%03d] (rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d, should be=%d)",
bufnum,
buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
buf->tag.rnode.relNode,
buf->tag.blockNum, buf->flags,
buf->refcount, PrivateRefCount[bufnum], shouldBe);
/* If it's less, we're in a heap o' trouble */
if (PrivateRefCount[bufnum] <= shouldBe)
elog(FATAL, "buffer refcount was decreased by subtransaction");
if (shouldBe > 0)
{
/* We still keep the shared-memory pin */
PrivateRefCount[bufnum] = shouldBe;
}
else
{
PrivateRefCount[bufnum] = 1; /* make sure we release shared pin */
LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
UnpinBuffer(buf);
LWLockRelease(BufMgrLock);
Assert(PrivateRefCount[bufnum] == 0);
}
}
/*
* FlushBufferPool
*

View File

@ -8,16 +8,16 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/ipc/ipci.c,v 1.68 2004/05/29 22:48:20 tgl Exp $
* $PostgreSQL: pgsql/src/backend/storage/ipc/ipci.c,v 1.69 2004/07/01 00:50:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "miscadmin.h"
#include "access/clog.h"
#include "access/subtrans.h"
#include "access/xlog.h"
#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/bufmgr.h"
#include "storage/freespace.h"
@ -70,6 +70,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate,
size += LockShmemSize(maxBackends);
size += XLOGShmemSize();
size += CLOGShmemSize();
size += SUBTRANSShmemSize();
size += LWLockShmemSize();
size += SInvalShmemSize(maxBackends);
size += FreeSpaceShmemSize();
@ -133,6 +134,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate,
*/
XLOGShmemInit();
CLOGShmemInit();
SUBTRANSShmemInit();
InitBufferPool();
/*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/ipc/sinval.c,v 1.64 2004/06/02 21:29:28 momjian Exp $
* $PostgreSQL: pgsql/src/backend/storage/ipc/sinval.c,v 1.65 2004/07/01 00:50:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -16,6 +16,8 @@
#include <signal.h>
#include "access/subtrans.h"
#include "access/transam.h"
#include "commands/async.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@ -428,20 +430,40 @@ DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself)
/*
* TransactionIdIsInProgress -- is given transaction running by some backend
*
* There are three possibilities for finding a running transaction:
*
* 1. the given Xid is a main transaction Id. We will find this out cheaply
* by looking at the PGPROC struct for each backend.
*
* 2. the given Xid is one of the cached subxact Xids in the PGPROC array.
* We can find this out cheaply too.
*
* 3. Search the SubTrans tree. This is the slowest, but sadly it has to be
* done always if the other two failed.
*
* 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)
{
bool result = false;
SISeg *segP = shmInvalBuffer;
ProcState *stateP = segP->procState;
int index;
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 (index = 0; index < segP->lastBackend; index++)
for (i = 0; i < segP->lastBackend; i++)
{
SHMEM_OFFSET pOffset = stateP[index].procStruct;
SHMEM_OFFSET pOffset = stateP[i].procStruct;
if (pOffset != INVALID_OFFSET)
{
@ -450,16 +472,71 @@ TransactionIdIsInProgress(TransactionId xid)
/* Fetch xid just once - see GetNewTransactionId */
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))
return true;
}
return result;
}
@ -596,7 +673,7 @@ GetSnapshotData(Snapshot snapshot, bool serializable)
* This does open a possibility for avoiding repeated malloc/free:
* since MaxBackends does not change at runtime, we can simply reuse
* the previous xip array if any. (This relies on the fact that all
* calls pass static SnapshotData structs.)
* callers pass static SnapshotData structs.)
*/
if (snapshot->xip == NULL)
{

View File

@ -8,13 +8,14 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.63 2004/05/28 05:13:04 tgl Exp $
* $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.64 2004/07/01 00:50:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
@ -333,19 +334,21 @@ XactLockTableInsert(TransactionId xid)
* XactLockTableWait
*
* Wait for the specified transaction to commit or abort.
* We actually wait on the topmost transaction of the transaction tree.
*/
void
XactLockTableWait(TransactionId xid)
{
LOCKTAG tag;
TransactionId myxid = GetCurrentTransactionId();
TransactionId waitXid = SubTransGetTopmostTransaction(xid);
Assert(!TransactionIdEquals(xid, myxid));
Assert(!SubTransXidsHaveCommonAncestor(waitXid, myxid));
MemSet(&tag, 0, sizeof(tag));
tag.relId = XactLockTableId;
tag.dbId = InvalidOid;
tag.objId.xid = xid;
tag.objId.xid = waitXid;
if (!LockAcquire(LockTableId, &tag, myxid,
ShareLock, false))
@ -355,8 +358,13 @@ XactLockTableWait(TransactionId xid)
/*
* Transaction was committed/aborted/crashed - we have to update
* pg_clog if transaction is still marked as running.
* pg_clog if transaction is still marked as running. If it's a
* subtransaction, we can update the parent status too.
*/
if (!TransactionIdDidCommit(xid) && !TransactionIdDidAbort(xid))
TransactionIdAbort(xid);
if (!TransactionIdDidCommit(waitXid) && !TransactionIdDidAbort(waitXid))
{
TransactionIdAbort(waitXid);
if (waitXid != xid)
TransactionIdAbort(xid);
}
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/lmgr/lock.c,v 1.133 2004/06/05 19:48:08 tgl Exp $
* $PostgreSQL: pgsql/src/backend/storage/lmgr/lock.c,v 1.134 2004/07/01 00:50:59 tgl Exp $
*
* NOTES
* Outside modules can create a lock table and acquire/release
@ -23,7 +23,7 @@
* Interface:
*
* LockAcquire(), LockRelease(), LockMethodTableInit(),
* LockMethodTableRename(), LockReleaseAll,
* LockMethodTableRename(), LockReleaseAll(),
* LockCheckConflicts(), GrantLock()
*
*-------------------------------------------------------------------------
@ -1129,19 +1129,25 @@ LockRelease(LOCKMETHODID lockmethodid, LOCKTAG *locktag,
}
/*
* LockReleaseAll -- Release all locks in a process's lock list.
* LockReleaseAll -- Release all locks of the specified lock method that
* are held by the specified process.
*
* Well, not really *all* locks.
* Well, not necessarily *all* locks. The available behaviors are:
*
* If 'allxids' is TRUE, all locks of the specified lock method are
* released, regardless of transaction affiliation.
* which == ReleaseAll: release all locks regardless of transaction
* affiliation.
*
* If 'allxids' is FALSE, all locks of the specified lock method and
* specified XID are released.
* which == ReleaseAllExceptSession: release all locks with Xid != 0
* (zero is the Xid used for "session" locks).
*
* which == ReleaseGivenXids: release only locks whose Xids appear in
* the xids[] array (of length nxids).
*
* xids/nxids are ignored when which != ReleaseGivenXids.
*/
bool
LockReleaseAll(LOCKMETHODID lockmethodid, PGPROC *proc,
bool allxids, TransactionId xid)
LockReleaseWhich which, int nxids, TransactionId *xids)
{
SHM_QUEUE *procHolders = &(proc->procHolders);
PROCLOCK *proclock;
@ -1190,8 +1196,25 @@ LockReleaseAll(LOCKMETHODID lockmethodid, PGPROC *proc,
if (LOCK_LOCKMETHOD(*lock) != lockmethodid)
goto next_item;
/* If not allxids, ignore items that are of the wrong xid */
if (!allxids && !TransactionIdEquals(xid, proclock->tag.xid))
if (which == ReleaseGivenXids)
{
/* Ignore locks with an Xid not in the list */
bool release = false;
for (i = 0; i < nxids; i++)
{
if (TransactionIdEquals(proclock->tag.xid, xids[i]))
{
release = true;
break;
}
}
if (!release)
goto next_item;
}
/* Ignore locks with Xid=0 unless we are asked to release All locks */
else if (TransactionIdEquals(proclock->tag.xid, InvalidTransactionId)
&& which != ReleaseAll)
goto next_item;
PROCLOCK_PRINT("LockReleaseAll", proclock);

View File

@ -15,13 +15,14 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/lmgr/lwlock.c,v 1.20 2004/06/11 16:43:24 tgl Exp $
* $PostgreSQL: pgsql/src/backend/storage/lmgr/lwlock.c,v 1.21 2004/07/01 00:50:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/clog.h"
#include "access/subtrans.h"
#include "storage/lwlock.h"
#include "storage/proc.h"
#include "storage/spin.h"
@ -111,6 +112,9 @@ NumLWLocks(void)
/* clog.c needs one per CLOG buffer + one control lock */
numLocks += NUM_CLOG_BUFFERS + 1;
/* subtrans.c needs one per SubTrans buffer + one control lock */
numLocks += NUM_SUBTRANS_BUFFERS + 1;
/* Perhaps create a few more for use by user-defined modules? */
return numLocks;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.148 2004/05/29 22:48:20 tgl Exp $
* $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.149 2004/07/01 00:50:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -380,26 +380,34 @@ LockWaitCancel(void)
/*
* ProcReleaseLocks() -- release locks associated with current transaction
* at transaction commit or abort
* at main transaction and subtransaction commit or abort
*
* At commit, we release only locks tagged with the current transaction's XID,
* leaving those marked with XID 0 (ie, session locks) undisturbed. At abort,
* we release all locks including XID 0, because we need to clean up after
* a failure. This logic will need extension if we ever support nested
* transactions.
* The options for which locks to release are the same as for the underlying
* LockReleaseAll() function.
*
* Note that user locks are not released in either case.
* Notes:
*
* At main transaction commit, we release all locks except session locks.
* At main transaction abort, we release all locks including session locks;
* this lets us clean up after a VACUUM FULL failure.
*
* At subtransaction commit, we don't release any locks (so this func is not
* called at all); we will defer the releasing to the parent transaction.
* At subtransaction abort, we release all locks held by the subtransaction;
* this is implemented by passing in the Xids of the failed subxact and its
* children in the xids[] array.
*
* Note that user locks are not released in any case.
*/
void
ProcReleaseLocks(bool isCommit)
ProcReleaseLocks(LockReleaseWhich which, int nxids, TransactionId *xids)
{
if (!MyProc)
return;
/* If waiting, get off wait queue (should only be needed after error) */
LockWaitCancel();
/* Release locks */
LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc,
!isCommit, GetCurrentTransactionId());
LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc, which, nxids, xids);
}
@ -432,11 +440,11 @@ ProcKill(int code, Datum arg)
LockWaitCancel();
/* Remove from the standard lock table */
LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc, true, InvalidTransactionId);
LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc, ReleaseAll, 0, NULL);
#ifdef USER_LOCKS
/* Remove from the user lock table */
LockReleaseAll(USER_LOCKMETHOD, MyProc, true, InvalidTransactionId);
LockReleaseAll(USER_LOCKMETHOD, MyProc, ReleaseAll, 0, NULL);
#endif
SpinLockAcquire(ProcStructLock);

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/smgr/smgr.c,v 1.74 2004/06/18 06:13:37 tgl Exp $
* $PostgreSQL: pgsql/src/backend/storage/smgr/smgr.c,v 1.75 2004/07/01 00:51:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -80,9 +80,10 @@ static HTAB *SMgrRelationHash = NULL;
* executed immediately, but is just entered in the list. When and if
* the transaction commits, we can delete the physical file.
*
* NOTE: the list is kept in TopMemoryContext to be sure it won't disappear
* unbetimes. It'd probably be OK to keep it in TopTransactionContext,
* but I'm being paranoid.
* The list is kept in CurTransactionContext. In subtransactions, each
* subtransaction has its own list in its own CurTransactionContext, but
* successful subtransactions attach their lists to their parent's list.
* Failed subtransactions can immediately execute the abort-time actions.
*/
typedef struct PendingRelDelete
@ -91,10 +92,11 @@ typedef struct PendingRelDelete
int which; /* which storage manager? */
bool isTemp; /* is it a temporary relation? */
bool atCommit; /* T=delete at commit; F=delete at abort */
struct PendingRelDelete *next; /* linked-list link */
} PendingRelDelete;
static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
static List *pendingDeletes = NIL; /* head of linked list */
static List *upperPendingDeletes = NIL; /* list of upper-xact lists */
/*
@ -305,6 +307,7 @@ smgrcreate(SMgrRelation reln, bool isTemp, bool isRedo)
XLogRecData rdata;
xl_smgr_create xlrec;
PendingRelDelete *pending;
MemoryContext old_cxt;
if (! (*(smgrsw[reln->smgr_which].smgr_create)) (reln, isRedo))
ereport(ERROR,
@ -332,14 +335,17 @@ smgrcreate(SMgrRelation reln, bool isTemp, bool isRedo)
lsn = XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLOG_NO_TRAN, &rdata);
/* Add the relation to the list of stuff to delete at abort */
pending = (PendingRelDelete *)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
old_cxt = MemoryContextSwitchTo(CurTransactionContext);
pending = (PendingRelDelete *) palloc(sizeof(PendingRelDelete));
pending->relnode = reln->smgr_rnode;
pending->which = reln->smgr_which;
pending->isTemp = isTemp;
pending->atCommit = false; /* delete if abort */
pending->next = pendingDeletes;
pendingDeletes = pending;
pendingDeletes = lcons(pending, pendingDeletes);
MemoryContextSwitchTo(old_cxt);
}
/*
@ -354,16 +360,20 @@ void
smgrscheduleunlink(SMgrRelation reln, bool isTemp)
{
PendingRelDelete *pending;
MemoryContext old_cxt;
/* Add the relation to the list of stuff to delete at commit */
pending = (PendingRelDelete *)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
old_cxt = MemoryContextSwitchTo(CurTransactionContext);
pending = (PendingRelDelete *) palloc(sizeof(PendingRelDelete));
pending->relnode = reln->smgr_rnode;
pending->which = reln->smgr_which;
pending->isTemp = isTemp;
pending->atCommit = true; /* delete if commit */
pending->next = pendingDeletes;
pendingDeletes = pending;
pendingDeletes = lcons(pending, pendingDeletes);
MemoryContextSwitchTo(old_cxt);
/*
* NOTE: if the relation was created in this transaction, it will now
@ -627,18 +637,21 @@ smgrimmedsync(SMgrRelation reln)
void
smgrDoPendingDeletes(bool isCommit)
{
while (pendingDeletes != NULL)
{
PendingRelDelete *pending = pendingDeletes;
ListCell *p;
foreach(p, pendingDeletes)
{
PendingRelDelete *pending = lfirst(p);
pendingDeletes = pending->next;
if (pending->atCommit == isCommit)
smgr_internal_unlink(pending->relnode,
pending->which,
pending->isTemp,
false);
pfree(pending);
}
/* We needn't free the cells since they are in CurTransactionContext */
pendingDeletes = NIL;
}
/*
@ -647,17 +660,22 @@ smgrDoPendingDeletes(bool isCommit)
* The return value is the number of relations scheduled for termination.
* *ptr is set to point to a freshly-palloc'd array of RelFileNodes.
* If there are no relations to be deleted, *ptr is set to NULL.
*
* Note that the list does not include anything scheduled for termination
* by upper-level transactions.
*/
int
smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
{
int nrels;
RelFileNode *rptr;
PendingRelDelete *pending;
ListCell *p;
nrels = 0;
for (pending = pendingDeletes; pending != NULL; pending = pending->next)
foreach(p, pendingDeletes)
{
PendingRelDelete *pending = lfirst(p);
if (pending->atCommit == forCommit)
nrels++;
}
@ -668,14 +686,69 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
}
rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode));
*ptr = rptr;
for (pending = pendingDeletes; pending != NULL; pending = pending->next)
foreach(p, pendingDeletes)
{
PendingRelDelete *pending = lfirst(p);
if (pending->atCommit == forCommit)
*rptr++ = pending->relnode;
}
return nrels;
}
/*
* AtSubStart_smgr() --- Take care of subtransaction start.
*
* Push empty state for the new subtransaction.
*/
void
AtSubStart_smgr(void)
{
MemoryContext old_cxt;
/* Keep the list-of-lists in TopTransactionContext for simplicity */
old_cxt = MemoryContextSwitchTo(TopTransactionContext);
upperPendingDeletes = lcons(pendingDeletes, upperPendingDeletes);
pendingDeletes = NIL;
MemoryContextSwitchTo(old_cxt);
}
/*
* AtSubCommit_smgr() --- Take care of subtransaction commit.
*
* Reassign all items in the pending deletes list to the parent transaction.
*/
void
AtSubCommit_smgr(void)
{
List *parentPendingDeletes;
parentPendingDeletes = (List *) linitial(upperPendingDeletes);
upperPendingDeletes = list_delete_first(upperPendingDeletes);
pendingDeletes = list_concat(parentPendingDeletes, pendingDeletes);
}
/*
* AtSubAbort_smgr() --- Take care of subtransaction abort.
*
* Delete created relations and forget about deleted relations.
* We can execute these operations immediately because we know this
* subtransaction will not commit.
*/
void
AtSubAbort_smgr(void)
{
smgrDoPendingDeletes(false);
/* Must pop the stack, too */
pendingDeletes = (List *) linitial(upperPendingDeletes);
upperPendingDeletes = list_delete_first(upperPendingDeletes);
}
/*
* smgrcommit() -- Prepare to commit changes made during the current
* transaction.

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.421 2004/06/24 21:03:08 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.422 2004/07/01 00:51:11 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@ -841,6 +841,7 @@ exec_simple_query(const char *query_string)
TransactionStmt *stmt = (TransactionStmt *) parsetree;
if (stmt->kind == TRANS_STMT_COMMIT ||
stmt->kind == TRANS_STMT_BEGIN ||
stmt->kind == TRANS_STMT_ROLLBACK)
allowit = true;
}
@ -1161,6 +1162,7 @@ exec_parse_message(const char *query_string, /* string to execute */
TransactionStmt *stmt = (TransactionStmt *) parsetree;
if (stmt->kind == TRANS_STMT_COMMIT ||
stmt->kind == TRANS_STMT_BEGIN ||
stmt->kind == TRANS_STMT_ROLLBACK)
allowit = true;
}
@ -1623,6 +1625,7 @@ exec_execute_message(const char *portal_name, long max_rows)
is_trans_stmt = true;
if (stmt->kind == TRANS_STMT_COMMIT ||
stmt->kind == TRANS_STMT_BEGIN ||
stmt->kind == TRANS_STMT_ROLLBACK)
is_trans_exit = true;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/catcache.c,v 1.112 2004/05/26 04:41:40 neilc Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/catcache.c,v 1.113 2004/07/01 00:51:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -360,6 +360,8 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct)
/* free associated tuple data */
if (ct->tuple.t_data != NULL)
pfree(ct->tuple.t_data);
if (ct->prev_refcount != NULL)
pfree(ct->prev_refcount);
pfree(ct);
--cache->cc_ntup;
@ -394,6 +396,8 @@ CatCacheRemoveCList(CatCache *cache, CatCList *cl)
/* free associated tuple data */
if (cl->tuple.t_data != NULL)
pfree(cl->tuple.t_data);
if (cl->prev_refcount != NULL)
pfree(cl->prev_refcount);
pfree(cl);
}
@ -518,9 +522,9 @@ CreateCacheMemoryContext(void)
if (!CacheMemoryContext)
CacheMemoryContext = AllocSetContextCreate(TopMemoryContext,
"CacheMemoryContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
}
@ -560,6 +564,13 @@ AtEOXact_CatCache(bool isCommit)
cl->refcount = 0;
}
/*
* Reset the refcount stack. Drop the item count to zero,
* but don't deallocate the stack itself, so it can be used by
* future subtransactions.
*/
cl->numpushes = 0;
/* Clean up any now-deletable dead entries */
if (cl->dead)
CatCacheRemoveCList(ccp, cl);
@ -585,12 +596,174 @@ AtEOXact_CatCache(bool isCommit)
ct->refcount = 0;
}
/*
* Reset the refcount stack. Drop the item count to zero,
* but don't deallocate the stack itself, so it can be used by
* future subtransactions.
*/
ct->numpushes = 0;
/* Clean up any now-deletable dead entries */
if (ct->dead)
CatCacheRemoveCTup(ct->my_cache, ct);
}
}
/*
* AtSubStart_CatCache
*
* Saves reference counts of each entry at subtransaction start so they
* can be restored if the subtransaction later aborts.
*/
void
AtSubStart_CatCache(void)
{
CatCache *ccp;
Dlelem *elt,
*nextelt;
MemoryContext old_cxt;
old_cxt = MemoryContextSwitchTo(CacheMemoryContext);
/*
* Prepare CLists
*/
for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next)
{
for (elt = DLGetHead(&ccp->cc_lists); elt; elt = nextelt)
{
CatCList *cl = (CatCList *) DLE_VAL(elt);
nextelt = DLGetSucc(elt);
if (cl->numpushes == cl->numalloc)
{
if (cl->numalloc == 0)
{
cl->numalloc = 8;
cl->prev_refcount = palloc(sizeof(int) * cl->numalloc);
}
else
{
cl->numalloc *= 2;
cl->prev_refcount = repalloc(cl->prev_refcount, cl->numalloc * sizeof(int));
}
}
cl->prev_refcount[cl->numpushes++] = cl->refcount;
}
}
/*
* Prepare CTuples
*/
for (elt = DLGetHead(&CacheHdr->ch_lrulist); elt; elt = nextelt)
{
CatCTup *ct = (CatCTup *) DLE_VAL(elt);
nextelt = DLGetSucc(elt);
if (ct->numpushes == ct->numalloc)
{
if (ct->numalloc == 0)
{
ct->numalloc = 8;
ct->prev_refcount = palloc(sizeof(int) * ct->numalloc);
}
else
{
ct->numalloc *= 2;
ct->prev_refcount = repalloc(ct->prev_refcount, sizeof(int) * ct->numalloc);
}
}
ct->prev_refcount[ct->numpushes++] = ct->refcount;
}
MemoryContextSwitchTo(old_cxt);
}
void
AtEOSubXact_CatCache(bool isCommit)
{
CatCache *ccp;
Dlelem *elt,
*nextelt;
/*
* Restore CLists
*/
for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next)
{
for (elt = DLGetHead(&ccp->cc_lists); elt; elt = nextelt)
{
CatCList *cl = (CatCList *) DLE_VAL(elt);
nextelt = DLGetSucc(elt);
/*
* During commit, check whether the count is what
* we expect.
*/
if (isCommit)
{
int expected_refcount;
if (cl->numpushes > 0)
expected_refcount = cl->prev_refcount[cl->numpushes - 1];
else
expected_refcount = 0;
if (cl->refcount != expected_refcount)
elog(WARNING, "catcache reference leak");
}
/*
* During abort we have to restore the original count;
* during commit, we have to restore in case of a leak,
* and it won't harm if this is the expected count.
*/
if (cl->numpushes > 0)
cl->refcount = cl->prev_refcount[--cl->numpushes];
else
cl->refcount = 0;
}
}
/*
* Prepare CTuples
*/
for (elt = DLGetHead(&CacheHdr->ch_lrulist); elt; elt = nextelt)
{
CatCTup *ct = (CatCTup *) DLE_VAL(elt);
nextelt = DLGetSucc(elt);
if (isCommit)
{
int expected_refcount;
if (ct->numpushes > 0)
expected_refcount = ct->prev_refcount[ct->numpushes - 1];
else
expected_refcount = 0;
if (ct->refcount != expected_refcount)
elog(WARNING, "catcache reference leak");
}
/*
* During abort we have to restore the original count;
* during commit, we have to restore in case of a leak,
* and it won't harm if this is the expected count.
*/
if (ct->numpushes > 0)
ct->refcount = ct->prev_refcount[--ct->numpushes];
else
ct->refcount = 0;
}
}
/*
* ResetCatalogCache
*
@ -1505,6 +1678,9 @@ SearchCatCacheList(CatCache *cache,
cl->my_cache = cache;
DLInitElem(&cl->cache_elem, (void *) cl);
cl->refcount = 1; /* count this first reference */
cl->prev_refcount = NULL;
cl->numpushes = 0;
cl->numalloc = 0;
cl->dead = false;
cl->ordered = ordered;
cl->nkeys = nkeys;
@ -1603,6 +1779,9 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
ct->dead = false;
ct->negative = negative;
ct->hash_value = hashValue;
ct->prev_refcount = NULL;
ct->numpushes = 0;
ct->numalloc = 0;
DLAddHead(&CacheHdr->ch_lrulist, &ct->lrulist_elem);
DLAddHead(&cache->cc_bucket[hashIndex], &ct->cache_elem);

View File

@ -33,6 +33,10 @@
* to record the transaction commit before sending SI messages, otherwise
* the other backends won't see our updated tuples as good.
*
* When a subtransaction aborts, we can process and discard any events
* it has queued. When a subtransaction commits, we just add its events
* to the pending lists of the parent transaction.
*
* In short, we need to remember until xact end every insert or delete
* of a tuple that might be in the system caches. Updates are treated as
* two events, delete + insert, for simplicity. (There are cases where
@ -66,15 +70,17 @@
* manipulating the init file is in relcache.c, but we keep track of the
* need for it here.
*
* All the request lists are kept in TopTransactionContext memory, since
* they need not live beyond the end of the current transaction.
* The request lists proper are kept in CurTransactionContext of their
* creating (sub)transaction, since they can be forgotten on abort of that
* transaction but must be kept till top-level commit otherwise. For
* simplicity we keep the controlling list-of-lists in TopTransactionContext.
*
*
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.62 2004/06/18 06:13:52 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.63 2004/07/01 00:51:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -95,7 +101,7 @@
* To minimize palloc traffic, we keep pending requests in successively-
* larger chunks (a slightly more sophisticated version of an expansible
* array). All request types can be stored as SharedInvalidationMessage
* records.
* records. The ordering of requests within a list is never significant.
*/
typedef struct InvalidationChunk
{
@ -112,12 +118,15 @@ typedef struct InvalidationListHeader
} InvalidationListHeader;
/*----------------
* Invalidation info is divided into two lists:
* Invalidation info is divided into two lists:
* 1) events so far in current command, not yet reflected to caches.
* 2) events in previous commands of current transaction; these have
* been reflected to local caches, and must be either broadcast to
* other backends or rolled back from local cache when we commit
* or abort the transaction.
* Actually, we need two such lists for each level of nested transaction,
* so that we can discard events from an aborted subtransaction. When
* a subtransaction commits, we append its lists to the parent's lists.
*
* The relcache-file-invalidated flag can just be a simple boolean,
* since we only act on it at transaction commit; we don't care which
@ -125,13 +134,22 @@ typedef struct InvalidationListHeader
*----------------
*/
/* head of current-command event list */
static InvalidationListHeader CurrentCmdInvalidMsgs;
typedef struct TransInvalidationInfo
{
/* Back link to parent transaction's info */
struct TransInvalidationInfo *parent;
/* head of previous-commands event list */
static InvalidationListHeader PriorCmdInvalidMsgs;
/* head of current-command event list */
InvalidationListHeader CurrentCmdInvalidMsgs;
static bool RelcacheInitFileInval; /* init file must be invalidated? */
/* head of previous-commands event list */
InvalidationListHeader PriorCmdInvalidMsgs;
/* init file must be invalidated? */
bool RelcacheInitFileInval;
} TransInvalidationInfo;
static TransInvalidationInfo *transInvalInfo = NULL;
/*
* Dynamically-registered callback functions. Current implementation
@ -176,7 +194,7 @@ AddInvalidationMessage(InvalidationChunk **listHdr,
/* First time through; create initial chunk */
#define FIRSTCHUNKSIZE 16
chunk = (InvalidationChunk *)
MemoryContextAlloc(TopTransactionContext,
MemoryContextAlloc(CurTransactionContext,
sizeof(InvalidationChunk) +
(FIRSTCHUNKSIZE - 1) *sizeof(SharedInvalidationMessage));
chunk->nitems = 0;
@ -190,7 +208,7 @@ AddInvalidationMessage(InvalidationChunk **listHdr,
int chunksize = 2 * chunk->maxitems;
chunk = (InvalidationChunk *)
MemoryContextAlloc(TopTransactionContext,
MemoryContextAlloc(CurTransactionContext,
sizeof(InvalidationChunk) +
(chunksize - 1) *sizeof(SharedInvalidationMessage));
chunk->nitems = 0;
@ -203,29 +221,6 @@ AddInvalidationMessage(InvalidationChunk **listHdr,
chunk->nitems++;
}
/*
* Free a list of inval message chunks.
*
* NOTE: when we are about to commit or abort a transaction, it's
* not really necessary to pfree the lists explicitly, since they will
* go away anyway when TopTransactionContext is destroyed.
*/
static void
FreeInvalidationMessageList(InvalidationChunk **listHdr)
{
InvalidationChunk *chunk = *listHdr;
*listHdr = NULL;
while (chunk != NULL)
{
InvalidationChunk *nextchunk = chunk->next;
pfree(chunk);
chunk = nextchunk;
}
}
/*
* Append one list of invalidation message chunks to another, resetting
* the source chunk-list pointer to NULL.
@ -331,31 +326,6 @@ AppendInvalidationMessages(InvalidationListHeader *dest,
AppendInvalidationMessageList(&dest->rclist, &src->rclist);
}
/*
* Reset an invalidation list to empty
*
* physicalFree may be set false if caller knows transaction is ending
*/
static void
DiscardInvalidationMessages(InvalidationListHeader *hdr, bool physicalFree)
{
if (physicalFree)
{
/* Physically pfree the list data */
FreeInvalidationMessageList(&hdr->cclist);
FreeInvalidationMessageList(&hdr->rclist);
}
else
{
/*
* Assume the storage will go away at xact end, just reset
* pointers
*/
hdr->cclist = NULL;
hdr->rclist = NULL;
}
}
/*
* Execute the given function for all the messages in an invalidation list.
* The list is not altered.
@ -386,7 +356,7 @@ RegisterCatcacheInvalidation(int cacheId,
ItemPointer tuplePtr,
Oid dbId)
{
AddCatcacheInvalidationMessage(&CurrentCmdInvalidMsgs,
AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
cacheId, hashValue, tuplePtr, dbId);
}
@ -398,7 +368,7 @@ RegisterCatcacheInvalidation(int cacheId,
static void
RegisterRelcacheInvalidation(Oid dbId, Oid relId, RelFileNode physId)
{
AddRelcacheInvalidationMessage(&CurrentCmdInvalidMsgs,
AddRelcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
dbId, relId, physId);
/*
@ -406,7 +376,7 @@ RegisterRelcacheInvalidation(Oid dbId, Oid relId, RelFileNode physId)
* relcache init file, mark that we need to zap that file at commit.
*/
if (RelationIdIsInInitFile(relId))
RelcacheInitFileInval = true;
transInvalInfo->RelcacheInitFileInval = true;
}
/*
@ -619,8 +589,38 @@ AcceptInvalidationMessages(void)
}
/*
* AtEOXactInvalidationMessages
* Process queued-up invalidation messages at end of transaction.
* AtStart_Inval
* Initialize inval lists at start of a main transaction.
*/
void
AtStart_Inval(void)
{
Assert(transInvalInfo == NULL);
transInvalInfo = (TransInvalidationInfo *)
MemoryContextAllocZero(TopTransactionContext,
sizeof(TransInvalidationInfo));
}
/*
* AtSubStart_Inval
* Initialize inval lists at start of a subtransaction.
*/
void
AtSubStart_Inval(void)
{
TransInvalidationInfo *myInfo;
Assert(transInvalInfo != NULL);
myInfo = (TransInvalidationInfo *)
MemoryContextAllocZero(TopTransactionContext,
sizeof(TransInvalidationInfo));
myInfo->parent = transInvalInfo;
transInvalInfo = myInfo;
}
/*
* AtEOXact_Inval
* Process queued-up invalidation messages at end of main transaction.
*
* If isCommit, we must send out the messages in our PriorCmdInvalidMsgs list
* to the shared invalidation message queue. Note that these will be read
@ -643,8 +643,11 @@ AcceptInvalidationMessages(void)
* This should be called as the last step in processing a transaction.
*/
void
AtEOXactInvalidationMessages(bool isCommit)
AtEOXact_Inval(bool isCommit)
{
/* Must be at top of stack */
Assert(transInvalInfo != NULL && transInvalInfo->parent == NULL);
if (isCommit)
{
/*
@ -652,28 +655,77 @@ AtEOXactInvalidationMessages(bool isCommit)
* and after we send the SI messages. However, we need not do
* anything unless we committed.
*/
if (RelcacheInitFileInval)
if (transInvalInfo->RelcacheInitFileInval)
RelationCacheInitFileInvalidate(true);
AppendInvalidationMessages(&PriorCmdInvalidMsgs,
&CurrentCmdInvalidMsgs);
AppendInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
&transInvalInfo->CurrentCmdInvalidMsgs);
ProcessInvalidationMessages(&PriorCmdInvalidMsgs,
ProcessInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
SendSharedInvalidMessage);
if (RelcacheInitFileInval)
if (transInvalInfo->RelcacheInitFileInval)
RelationCacheInitFileInvalidate(false);
}
else
{
ProcessInvalidationMessages(&PriorCmdInvalidMsgs,
ProcessInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
LocalExecuteInvalidationMessage);
}
RelcacheInitFileInval = false;
/* Need not free anything explicitly */
transInvalInfo = NULL;
}
DiscardInvalidationMessages(&PriorCmdInvalidMsgs, false);
DiscardInvalidationMessages(&CurrentCmdInvalidMsgs, false);
/*
* AtSubEOXact_Inval
* Process queued-up invalidation messages at end of subtransaction.
*
* If isCommit, process CurrentCmdInvalidMsgs if any (there probably aren't),
* and then attach both CurrentCmdInvalidMsgs and PriorCmdInvalidMsgs to the
* parent's PriorCmdInvalidMsgs list.
*
* If not isCommit, we are aborting, and must locally process the messages
* in PriorCmdInvalidMsgs. No messages need be sent to other backends.
* We can forget about CurrentCmdInvalidMsgs too, since those changes haven't
* touched the caches yet.
*
* In any case, pop the transaction stack. We need not physically free memory
* here, since CurTransactionContext is about to be emptied anyway
* (if aborting).
*/
void
AtSubEOXact_Inval(bool isCommit)
{
TransInvalidationInfo *myInfo = transInvalInfo;
/* Must be at non-top of stack */
Assert(myInfo != NULL && myInfo->parent != NULL);
if (isCommit)
{
/* If CurrentCmdInvalidMsgs still has anything, fix it */
CommandEndInvalidationMessages();
/* Pass up my inval messages to parent */
AppendInvalidationMessages(&myInfo->parent->PriorCmdInvalidMsgs,
&myInfo->PriorCmdInvalidMsgs);
/* Pending relcache inval becomes parent's problem too */
if (myInfo->RelcacheInitFileInval)
myInfo->parent->RelcacheInitFileInval = true;
}
else
{
ProcessInvalidationMessages(&myInfo->PriorCmdInvalidMsgs,
LocalExecuteInvalidationMessage);
}
/* Pop the transaction state stack */
transInvalInfo = myInfo->parent;
/* Need not free anything else explicitly */
pfree(myInfo);
}
/*
@ -687,27 +739,25 @@ AtEOXactInvalidationMessages(bool isCommit)
* current command. We then move the current-cmd list over to become part
* of the prior-cmds list.
*
* The isCommit = false case is not currently used, but may someday be
* needed to support rollback to a savepoint within a transaction.
*
* Note:
* This should be called during CommandCounterIncrement(),
* after we have advanced the command ID.
*/
void
CommandEndInvalidationMessages(bool isCommit)
CommandEndInvalidationMessages(void)
{
if (isCommit)
{
ProcessInvalidationMessages(&CurrentCmdInvalidMsgs,
LocalExecuteInvalidationMessage);
AppendInvalidationMessages(&PriorCmdInvalidMsgs,
&CurrentCmdInvalidMsgs);
}
else
{
/* XXX what needs to be done here? */
}
/*
* You might think this shouldn't be called outside any transaction,
* but bootstrap does it, and also ABORT issued when not in a transaction.
* So just quietly return if no state to work on.
*/
if (transInvalInfo == NULL)
return;
ProcessInvalidationMessages(&transInvalInfo->CurrentCmdInvalidMsgs,
LocalExecuteInvalidationMessage);
AppendInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
&transInvalInfo->CurrentCmdInvalidMsgs);
}
/*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.205 2004/06/18 06:13:52 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.206 2004/07/01 00:51:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -273,6 +273,8 @@ static void IndexSupportInitialize(Form_pg_index iform,
static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numStrats,
StrategyNumber numSupport);
static inline void RelationPushReferenceCount(Relation rel);
static inline void RelationPopReferenceCount(Relation rel);
/*
@ -1678,6 +1680,8 @@ RelationClearRelation(Relation relation, bool rebuild)
list_free(relation->rd_indexlist);
if (relation->rd_indexcxt)
MemoryContextDelete(relation->rd_indexcxt);
if (relation->rd_prevrefcnt)
pfree(relation->rd_prevrefcnt);
/*
* If we're really done with the relcache entry, blow it away. But if
@ -1968,7 +1972,7 @@ RelationCacheInvalidate(void)
* we must reset refcnts before handling pending invalidations.
*/
void
AtEOXact_RelationCache(bool commit)
AtEOXact_RelationCache(bool isCommit)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
@ -1993,7 +1997,7 @@ AtEOXact_RelationCache(bool commit)
*/
if (relation->rd_isnew)
{
if (commit)
if (isCommit)
relation->rd_isnew = false;
else
{
@ -2019,7 +2023,7 @@ AtEOXact_RelationCache(bool commit)
*/
expected_refcnt = relation->rd_isnailed ? 1 : 0;
if (commit)
if (isCommit)
{
if (relation->rd_refcnt != expected_refcnt &&
!IsBootstrapProcessingMode())
@ -2036,6 +2040,12 @@ AtEOXact_RelationCache(bool commit)
RelationSetReferenceCount(relation, expected_refcnt);
}
/*
* Reset the refcount stack. Just drop the item count; don't deallocate
* the stack itself so it can be reused by future subtransactions.
*/
relation->rd_numpushed = 0;
/*
* Flush any temporary index list.
*/
@ -2048,6 +2058,131 @@ AtEOXact_RelationCache(bool commit)
}
}
/*
* RelationPushReferenceCount
*
* Push the current reference count into the stack. Don't modify the
* reference count itself.
*/
static inline void
RelationPushReferenceCount(Relation rel)
{
/* Enlarge the stack if we run out of space. */
if (rel->rd_numpushed == rel->rd_numalloc)
{
MemoryContext old_cxt = MemoryContextSwitchTo(CacheMemoryContext);
if (rel->rd_numalloc == 0)
{
rel->rd_numalloc = 8;
rel->rd_prevrefcnt = palloc(rel->rd_numalloc * sizeof(int));
}
else
{
rel->rd_numalloc *= 2;
rel->rd_prevrefcnt = repalloc(rel->rd_prevrefcnt, rel->rd_numalloc * sizeof(int));
}
MemoryContextSwitchTo(old_cxt);
}
rel->rd_prevrefcnt[rel->rd_numpushed++] = rel->rd_refcnt;
}
/*
* RelationPopReferenceCount
*
* Pop the latest stored reference count. If there is none, drop it
* to zero; the entry was created in the current subtransaction.
*/
static inline void
RelationPopReferenceCount(Relation rel)
{
if (rel->rd_numpushed == 0)
{
rel->rd_refcnt = rel->rd_isnailed ? 1 : 0;
return;
}
rel->rd_refcnt = rel->rd_prevrefcnt[--rel->rd_numpushed];
}
/*
* AtEOSubXact_RelationCache
*/
void
AtEOSubXact_RelationCache(bool isCommit)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
/* We'd better not be bootstrapping. */
Assert(!IsBootstrapProcessingMode());
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
Relation relation = idhentry->reldesc;
/*
* During subtransaction commit, we first check whether the
* current refcount is correct: if there is no item in the stack,
* the relcache entry was created during this subtransaction, it should
* be 0 (or 1 for nailed relations). If the stack has at least one
* item, the expected count is whatever that item is.
*/
if (isCommit)
{
int expected_refcnt;
if (relation->rd_numpushed == 0)
expected_refcnt = relation->rd_isnailed ? 1 : 0;
else
expected_refcnt = relation->rd_prevrefcnt[relation->rd_numpushed - 1];
if (relation->rd_refcnt != expected_refcnt)
{
elog(WARNING, "relcache reference leak: relation \"%s\" has refcnt %d instead of %d",
RelationGetRelationName(relation),
relation->rd_refcnt, expected_refcnt);
}
}
/*
* On commit, the expected count is stored so there's no harm in
* popping it (and we may need to fix if there was a leak); and during
* abort, the correct refcount has to be restored.
*/
RelationPopReferenceCount(relation);
}
}
/*
* AtSubStart_RelationCache
*
* At subtransaction start, we push the current reference count into
* the refcount stack, so it can be restored if the subtransaction aborts.
*/
void
AtSubStart_RelationCache(void)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
/* We'd better not be bootstrapping. */
Assert(!IsBootstrapProcessingMode());
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
Relation relation = idhentry->reldesc;
RelationPushReferenceCount(relation);
}
}
/*
* RelationBuildLocalRelation
* Build a relcache entry for an about-to-be-created relation,

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.134 2004/06/18 06:13:54 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.135 2004/07/01 00:51:20 tgl Exp $
*
*
*-------------------------------------------------------------------------
@ -27,7 +27,6 @@
#include "catalog/pg_database.h"
#include "catalog/pg_shadow.h"
#include "catalog/pg_tablespace.h"
#include "commands/trigger.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
@ -350,12 +349,6 @@ InitPostgres(const char *dbname, const char *username)
/* Initialize portal manager */
EnablePortalManager();
/*
* Initialize the deferred trigger manager --- must happen before
* first transaction start.
*/
DeferredTriggerInit();
/* start a new transaction here before access to db */
if (!bootstrap)
StartTransactionCommand();

View File

@ -1,4 +1,4 @@
$PostgreSQL: pgsql/src/backend/utils/misc/README,v 1.4 2004/01/19 19:04:40 tgl Exp $
$PostgreSQL: pgsql/src/backend/utils/misc/README,v 1.5 2004/07/01 00:51:24 tgl Exp $
GUC IMPLEMENTATION NOTES
@ -68,49 +68,66 @@ SET on transaction abort, and rollback of SET LOCAL at transaction end
would be effective had there never been any SET commands in the current
session.
To handle these cases we must keep track of as many as four distinct
values for each variable. They are:
To handle these cases we must keep track of many distinct values for each
variable. The primary values are:
* actual variable contents always the current effective value
* reset_value the value to use for RESET
* session_value the "committed" setting for the session
* tentative_value the uncommitted result of SET
During initialization we set the first three of these (actual, reset_value,
and session_value) based on whichever non-interactive source has the
highest priority. All three will have the same value.
The reason we need a tentative_value separate from the actual value is
that when a transaction does SET followed by SET LOCAL, the actual value
will now be the LOCAL value, but we want to remember the prior SET so that
that value is restored at transaction commit.
In addition, for each level of transaction (possibly nested) we have to
remember the transaction-entry-time actual and tentative values, in case
we need to restore them at transaction end. (The RESET value is essentially
non-transactional, so it doesn't have to be stacked.) For efficiency these
stack entries are not constructed until/unless the variable is actually SET
within a particular transaction.
During initialization we set the actual value and reset_value based on
whichever non-interactive source has the highest priority. They will
have the same value. The tentative_value is not meaningful at this point.
A SET command starts by stacking the existing actual and tentative values
if this hasn't already been done within the current transaction. Then:
A SET LOCAL command sets the actual variable (and nothing else). At
transaction end, the session_value is used to restore the actual variable
to its pre-transaction value.
transaction end, the stacked values are used to restore the GUC entry
to its pre-transaction state.
A SET (or SET SESSION) command sets the actual variable, and if no error,
then sets the tentative_value. If the transaction commits, the
tentative_value is assigned to the session_value and the actual variable
(which could by now be different, if the SET was followed by SET LOCAL).
If the transaction aborts, the tentative_value is discarded and the
actual variable is restored from the session_value.
tentative_value is assigned again to the actual variable (which could by
now be different, if the SET was followed by SET LOCAL). If the
transaction aborts, the stacked values are used to restore the GUC entry
to its pre-transaction state.
In the case of SET within nested subtransactions, at each commit the
tentative_value propagates out to the next transaction level. It will
be thrown away at abort of any level, or after exiting the top transaction.
RESET is executed like a SET, but using the reset_value as the desired new
value. (We do not provide a RESET LOCAL command, but SET LOCAL TO DEFAULT
has the same behavior that RESET LOCAL would.) The source associated with
the reset_value also becomes associated with the actual and session values.
the reset_value also becomes associated with the actual and tentative values.
If SIGHUP is received, the GUC code rereads the postgresql.conf
configuration file (this does not happen in the signal handler, but at
next return to main loop; note that it can be executed while within a
transaction). New values from postgresql.conf are assigned to actual
variable, reset_value, and session_value, but only if each of these has a
current source priority <= PGC_S_FILE. (It is thus possible for
reset_value to track the config-file setting even if there is currently
a different interactive value of the actual variable.)
variable, reset_value, and stacked actual values, but only if each of
these has a current source priority <= PGC_S_FILE. (It is thus possible
for reset_value to track the config-file setting even if there is
currently a different interactive value of the actual variable.)
Note that tentative_value is unused and undefined except between a SET
command and the end of the transaction. Also notice that we must track
the source associated with each of the four values.
the source associated with each one of the values.
The assign_hook and show_hook routines work only with the actual variable,
and are not directly aware of the additional values maintained by GUC.
@ -129,9 +146,9 @@ pstrdup/palloc mechanisms. We would need to keep them in a permanent
context anyway, and strdup gives us more control over handling
out-of-memory failures.
We allow a variable's actual value, reset_val, session_val, and
tentative_val to point at the same storage. This makes it slightly harder
to free space (must test that the value to be freed isn't equal to any of
the other three pointers). The main advantage is that we never need to
strdup during transaction commit/abort, so cannot cause an out-of-memory
failure there.
We allow a string variable's actual value, reset_val, tentative_val, and
stacked copies of same to point at the same storage. This makes it
slightly harder to free space (must test whether a value to be freed isn't
equal to any of the other pointers in the GUC entry or associated stack
items). The main advantage is that we never need to strdup during
transaction commit/abort, so cannot cause an out-of-memory failure there.

View File

@ -10,17 +10,16 @@
* Written by Peter Eisentraut <peter_e@gmx.net>.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.211 2004/06/11 03:54:54 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.212 2004/07/01 00:51:24 tgl Exp $
*
*--------------------------------------------------------------------
*/
#include "postgres.h"
#include <errno.h>
#include <ctype.h>
#include <float.h>
#include <limits.h>
#include <unistd.h>
#include <ctype.h>
#include "utils/guc.h"
#include "utils/guc_tables.h"
@ -54,6 +53,7 @@
#include "tcop/tcopprot.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "pgstat.h"
@ -105,6 +105,7 @@ static const char *assign_custom_variable_classes(const char *newval, bool doit,
GucSource source);
static bool assign_stage_log_stats(bool newval, bool doit, GucSource source);
static bool assign_log_stats(bool newval, bool doit, GucSource source);
static bool assign_transaction_read_only(bool newval, bool doit, GucSource source);
/*
@ -174,45 +175,6 @@ static int max_identifier_length;
static int block_size;
static bool integer_datetimes;
/* Macros for freeing malloc'd pointers only if appropriate to do so */
/* Some of these tests are probably redundant, but be safe ... */
#define SET_STRING_VARIABLE(rec, newval) \
do { \
if (*(rec)->variable && \
*(rec)->variable != (rec)->reset_val && \
*(rec)->variable != (rec)->session_val && \
*(rec)->variable != (rec)->tentative_val) \
free(*(rec)->variable); \
*(rec)->variable = (newval); \
} while (0)
#define SET_STRING_RESET_VAL(rec, newval) \
do { \
if ((rec)->reset_val && \
(rec)->reset_val != *(rec)->variable && \
(rec)->reset_val != (rec)->session_val && \
(rec)->reset_val != (rec)->tentative_val) \
free((rec)->reset_val); \
(rec)->reset_val = (newval); \
} while (0)
#define SET_STRING_SESSION_VAL(rec, newval) \
do { \
if ((rec)->session_val && \
(rec)->session_val != *(rec)->variable && \
(rec)->session_val != (rec)->reset_val && \
(rec)->session_val != (rec)->tentative_val) \
free((rec)->session_val); \
(rec)->session_val = (newval); \
} while (0)
#define SET_STRING_TENTATIVE_VAL(rec, newval) \
do { \
if ((rec)->tentative_val && \
(rec)->tentative_val != *(rec)->variable && \
(rec)->tentative_val != (rec)->reset_val && \
(rec)->tentative_val != (rec)->session_val) \
free((rec)->tentative_val); \
(rec)->tentative_val = (newval); \
} while (0)
/*
* Displayable names for context types (enum GucContext)
@ -801,7 +763,7 @@ static struct config_bool ConfigureNamesBool[] =
GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
},
&XactReadOnly,
false, NULL, NULL
false, assign_transaction_read_only, NULL
},
{
{"add_missing_from", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
@ -1766,14 +1728,13 @@ static const char * const map_old_guc_names[] = {
*/
static struct config_generic **guc_variables;
/* Current number of variables contained in the vector
*/
/* Current number of variables contained in the vector */
static int num_guc_variables;
/* Vector capacity
*/
/* Vector capacity */
static int size_guc_variables;
static bool guc_dirty; /* TRUE if need to do commit/abort work */
static bool reporting_enabled; /* TRUE to enable GUC_REPORT */
@ -1783,14 +1744,71 @@ static char *guc_string_workspace; /* for avoiding memory leaks */
static int guc_var_compare(const void *a, const void *b);
static int guc_name_compare(const char *namea, const char *nameb);
static void push_old_value(struct config_generic *gconf);
static void ReportGUCOption(struct config_generic * record);
static char *_ShowOption(struct config_generic * record);
struct config_generic** get_guc_variables()
/*
* Support for assigning to a field of a string GUC item. Free the prior
* value if it's not referenced anywhere else in the item (including stacked
* states).
*/
static void
set_string_field(struct config_string *conf, char **field, char *newval)
{
char *oldval = *field;
GucStack *stack;
/* Do the assignment */
*field = newval;
/* Exit if any duplicate references, or if old value was NULL anyway */
if (oldval == NULL ||
oldval == *(conf->variable) ||
oldval == conf->reset_val ||
oldval == conf->tentative_val)
return;
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
if (oldval == stack->tentative_val.stringval ||
oldval == stack->value.stringval)
return;
}
/* Not used anymore, so free it */
free(oldval);
}
/*
* Detect whether strval is referenced anywhere in a GUC string item
*/
static bool
string_field_used(struct config_string *conf, char *strval)
{
GucStack *stack;
if (strval == *(conf->variable) ||
strval == conf->reset_val ||
strval == conf->tentative_val)
return true;
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
if (strval == stack->tentative_val.stringval ||
strval == stack->value.stringval)
return true;
}
return false;
}
struct config_generic **
get_guc_variables(void)
{
return guc_variables;
}
/*
* Build the sorted array. This is split out so that it could be
* re-executed after startup (eg, we could allow loadable modules to
@ -2001,14 +2019,13 @@ find_option(const char *name)
return find_option(map_old_guc_names[i+1]);
}
/* Check if the name is qualified, and if so, check if the qualifier
/*
* Check if the name is qualified, and if so, check if the qualifier
* maps to a custom variable class.
*/
dot = strchr(name, GUC_QUALIFIER_SEPARATOR);
if(dot != NULL && is_custom_class(name, dot - name))
/*
* Add a placeholder variable for this name
*/
/* Add a placeholder variable for this name */
return (struct config_generic*)add_placeholder_variable(name);
/* Unknown name */
@ -2081,9 +2098,9 @@ InitializeGUCOptions(void)
gconf->status = 0;
gconf->reset_source = PGC_S_DEFAULT;
gconf->session_source = PGC_S_DEFAULT;
gconf->tentative_source = PGC_S_DEFAULT;
gconf->source = PGC_S_DEFAULT;
gconf->stack = NULL;
switch (gconf->vartype)
{
@ -2097,7 +2114,6 @@ InitializeGUCOptions(void)
elog(FATAL, "failed to initialize %s to %d",
conf->gen.name, (int) conf->reset_val);
*conf->variable = conf->reset_val;
conf->session_val = conf->reset_val;
break;
}
case PGC_INT:
@ -2119,7 +2135,6 @@ InitializeGUCOptions(void)
elog(FATAL, "failed to initialize %s to %d",
conf->gen.name, conf->reset_val);
*conf->variable = conf->reset_val;
conf->session_val = conf->reset_val;
break;
}
case PGC_REAL:
@ -2135,7 +2150,6 @@ InitializeGUCOptions(void)
elog(FATAL, "failed to initialize %s to %g",
conf->gen.name, conf->reset_val);
*conf->variable = conf->reset_val;
conf->session_val = conf->reset_val;
break;
}
case PGC_STRING:
@ -2150,7 +2164,6 @@ InitializeGUCOptions(void)
conf->assign_hook == assign_log_statement);
*conf->variable = NULL;
conf->reset_val = NULL;
conf->session_val = NULL;
conf->tentative_val = NULL;
if (conf->boot_val == NULL)
@ -2190,7 +2203,6 @@ InitializeGUCOptions(void)
}
}
*conf->variable = str;
conf->session_val = str;
break;
}
}
@ -2254,6 +2266,9 @@ ResetAllOptions(void)
if (gconf->source <= PGC_S_OVERRIDE)
continue;
/* Save old value to support transaction abort */
push_old_value(gconf);
switch (gconf->vartype)
{
case PGC_BOOL:
@ -2336,8 +2351,8 @@ ResetAllOptions(void)
}
}
SET_STRING_VARIABLE(conf, str);
SET_STRING_TENTATIVE_VAL(conf, str);
set_string_field(conf, conf->variable, str);
set_string_field(conf, &conf->tentative_val, str);
conf->gen.source = conf->gen.reset_source;
conf->gen.tentative_source = conf->gen.reset_source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
@ -2353,11 +2368,93 @@ ResetAllOptions(void)
/*
* Do GUC processing at transaction commit or abort.
* push_old_value
* Push previous state during first assignment to a GUC variable
* within a particular transaction.
*
* We have to be willing to "back-fill" the state stack if the first
* assignment occurs within a subtransaction nested several levels deep.
* This ensures that if an intermediate transaction aborts, it will have
* the proper value available to restore the setting to.
*/
static void
push_old_value(struct config_generic *gconf)
{
int my_level = GetCurrentTransactionNestLevel();
GucStack *stack;
/* If we're not inside a transaction, do nothing */
if (my_level == 0)
return;
for (;;)
{
/* Done if we already pushed it at this nesting depth */
if (gconf->stack && gconf->stack->nest_level >= my_level)
return;
/*
* We keep all the stack entries in TopTransactionContext so as to
* avoid allocation problems when a subtransaction back-fills stack
* entries for upper transaction levels.
*/
stack = (GucStack *) MemoryContextAlloc(TopTransactionContext,
sizeof(GucStack));
stack->prev = gconf->stack;
stack->nest_level = stack->prev ? stack->prev->nest_level + 1 : 1;
stack->status = gconf->status;
stack->tentative_source = gconf->tentative_source;
stack->source = gconf->source;
switch (gconf->vartype)
{
case PGC_BOOL:
stack->tentative_val.boolval =
((struct config_bool *) gconf)->tentative_val;
stack->value.boolval =
*((struct config_bool *) gconf)->variable;
break;
case PGC_INT:
stack->tentative_val.intval =
((struct config_int *) gconf)->tentative_val;
stack->value.intval =
*((struct config_int *) gconf)->variable;
break;
case PGC_REAL:
stack->tentative_val.realval =
((struct config_real *) gconf)->tentative_val;
stack->value.realval =
*((struct config_real *) gconf)->variable;
break;
case PGC_STRING:
stack->tentative_val.stringval =
((struct config_string *) gconf)->tentative_val;
stack->value.stringval =
*((struct config_string *) gconf)->variable;
break;
}
gconf->stack = stack;
/* Set state to indicate nothing happened yet within this level */
gconf->status = GUC_HAVE_STACK;
/* Ensure we remember to pop at end of xact */
guc_dirty = true;
}
}
/*
* Do GUC processing at transaction or subtransaction commit or abort.
*/
void
AtEOXact_GUC(bool isCommit)
AtEOXact_GUC(bool isCommit, bool isSubXact)
{
int my_level;
int i;
/* Quick exit if nothing's changed in this transaction */
@ -2371,15 +2468,56 @@ AtEOXact_GUC(bool isCommit)
guc_string_workspace = NULL;
}
my_level = GetCurrentTransactionNestLevel();
Assert(isSubXact ? (my_level > 1) : (my_level == 1));
for (i = 0; i < num_guc_variables; i++)
{
struct config_generic *gconf = guc_variables[i];
int my_status = gconf->status;
GucStack *stack = gconf->stack;
bool useTentative;
bool changed;
/* Skip if nothing's happened to this var in this transaction */
if (gconf->status == 0)
/*
* Skip if nothing's happened to this var in this transaction
*/
if (my_status == 0)
{
Assert(stack == NULL);
continue;
}
/* Assert that we stacked old value before changing it */
Assert(stack != NULL && (my_status & GUC_HAVE_STACK));
/* However, the last change may have been at an outer xact level */
if (stack->nest_level < my_level)
continue;
Assert(stack->nest_level == my_level);
/*
* We will pop the stack entry. Start by restoring outer xact status
* (since we may want to modify it below). Be careful to use
* my_status to reference the inner xact status below this point...
*/
gconf->status = stack->status;
/*
* We have two cases:
*
* If commit and HAVE_TENTATIVE, set actual value to tentative
* (this is to override a SET LOCAL if one occurred later than SET).
* We keep the tentative value and propagate HAVE_TENTATIVE to
* the parent status, allowing the SET's effect to percolate up.
* (But if we're exiting the outermost transaction, we'll drop the
* HAVE_TENTATIVE bit below.)
*
* Otherwise, we have a transaction that aborted or executed only
* SET LOCAL (or no SET at all). In either case it should have no
* further effect, so restore both tentative and actual values from
* the stack entry.
*/
useTentative = isCommit && (my_status & GUC_HAVE_TENTATIVE) != 0;
changed = false;
switch (gconf->vartype)
@ -2387,126 +2525,190 @@ AtEOXact_GUC(bool isCommit)
case PGC_BOOL:
{
struct config_bool *conf = (struct config_bool *) gconf;
bool newval;
GucSource newsource;
if (isCommit && (conf->gen.status & GUC_HAVE_TENTATIVE))
if (useTentative)
{
conf->session_val = conf->tentative_val;
conf->gen.session_source = conf->gen.tentative_source;
newval = conf->tentative_val;
newsource = conf->gen.tentative_source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
}
else
{
newval = stack->value.boolval;
newsource = stack->source;
conf->tentative_val = stack->tentative_val.boolval;
conf->gen.tentative_source = stack->tentative_source;
}
if (*conf->variable != conf->session_val)
if (*conf->variable != newval)
{
if (conf->assign_hook)
if (!(*conf->assign_hook) (conf->session_val,
if (!(*conf->assign_hook) (newval,
true, PGC_S_OVERRIDE))
elog(LOG, "failed to commit %s",
conf->gen.name);
*conf->variable = conf->session_val;
*conf->variable = newval;
changed = true;
}
conf->gen.source = conf->gen.session_source;
conf->gen.status = 0;
conf->gen.source = newsource;
break;
}
case PGC_INT:
{
struct config_int *conf = (struct config_int *) gconf;
int newval;
GucSource newsource;
if (isCommit && (conf->gen.status & GUC_HAVE_TENTATIVE))
if (useTentative)
{
conf->session_val = conf->tentative_val;
conf->gen.session_source = conf->gen.tentative_source;
newval = conf->tentative_val;
newsource = conf->gen.tentative_source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
}
else
{
newval = stack->value.intval;
newsource = stack->source;
conf->tentative_val = stack->tentative_val.intval;
conf->gen.tentative_source = stack->tentative_source;
}
if (*conf->variable != conf->session_val)
if (*conf->variable != newval)
{
if (conf->assign_hook)
if (!(*conf->assign_hook) (conf->session_val,
if (!(*conf->assign_hook) (newval,
true, PGC_S_OVERRIDE))
elog(LOG, "failed to commit %s",
conf->gen.name);
*conf->variable = conf->session_val;
*conf->variable = newval;
changed = true;
}
conf->gen.source = conf->gen.session_source;
conf->gen.status = 0;
conf->gen.source = newsource;
break;
}
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
double newval;
GucSource newsource;
if (isCommit && (conf->gen.status & GUC_HAVE_TENTATIVE))
if (useTentative)
{
conf->session_val = conf->tentative_val;
conf->gen.session_source = conf->gen.tentative_source;
newval = conf->tentative_val;
newsource = conf->gen.tentative_source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
}
else
{
newval = stack->value.realval;
newsource = stack->source;
conf->tentative_val = stack->tentative_val.realval;
conf->gen.tentative_source = stack->tentative_source;
}
if (*conf->variable != conf->session_val)
if (*conf->variable != newval)
{
if (conf->assign_hook)
if (!(*conf->assign_hook) (conf->session_val,
if (!(*conf->assign_hook) (newval,
true, PGC_S_OVERRIDE))
elog(LOG, "failed to commit %s",
conf->gen.name);
*conf->variable = conf->session_val;
*conf->variable = newval;
changed = true;
}
conf->gen.source = conf->gen.session_source;
conf->gen.status = 0;
conf->gen.source = newsource;
break;
}
case PGC_STRING:
{
struct config_string *conf = (struct config_string *) gconf;
char *newval;
GucSource newsource;
if (isCommit && (conf->gen.status & GUC_HAVE_TENTATIVE))
if (useTentative)
{
SET_STRING_SESSION_VAL(conf, conf->tentative_val);
conf->gen.session_source = conf->gen.tentative_source;
conf->tentative_val = NULL; /* transfer ownership */
newval = conf->tentative_val;
newsource = conf->gen.tentative_source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
}
else
SET_STRING_TENTATIVE_VAL(conf, NULL);
if (*conf->variable != conf->session_val)
{
char *str = conf->session_val;
newval = stack->value.stringval;
newsource = stack->source;
set_string_field(conf, &conf->tentative_val,
stack->tentative_val.stringval);
conf->gen.tentative_source = stack->tentative_source;
}
if (*conf->variable != newval)
{
if (conf->assign_hook)
{
const char *newstr;
newstr = (*conf->assign_hook) (str, true,
newstr = (*conf->assign_hook) (newval, true,
PGC_S_OVERRIDE);
if (newstr == NULL)
elog(LOG, "failed to commit %s",
conf->gen.name);
else if (newstr != str)
else if (newstr != newval)
{
/*
* If newval should now be freed, it'll be
* taken care of below.
*
* See notes in set_config_option about
* casting
*/
str = (char *) newstr;
SET_STRING_SESSION_VAL(conf, str);
newval = (char *) newstr;
}
}
SET_STRING_VARIABLE(conf, str);
set_string_field(conf, conf->variable, newval);
changed = true;
}
conf->gen.source = conf->gen.session_source;
conf->gen.status = 0;
conf->gen.source = newsource;
/* Release stacked values if not used anymore */
set_string_field(conf, &stack->value.stringval,
NULL);
set_string_field(conf, &stack->tentative_val.stringval,
NULL);
/* Don't store tentative value separately after commit */
if (!isSubXact)
set_string_field(conf, &conf->tentative_val, NULL);
break;
}
}
/* Finish popping the state stack */
gconf->stack = stack->prev;
pfree(stack);
/*
* If we're now out of all xact levels, forget TENTATIVE status bit;
* there's nothing tentative about the value anymore.
*/
if (!isSubXact)
{
Assert(gconf->stack == NULL);
gconf->status = 0;
}
/* Report new value if we changed it */
if (changed && (gconf->flags & GUC_REPORT))
ReportGUCOption(gconf);
}
guc_dirty = false;
/*
* If we're now out of all xact levels, we can clear guc_dirty.
* (Note: we cannot reset guc_dirty when exiting a subtransaction,
* because we know that all outer transaction levels will have stacked
* values to deal with.)
*/
if (!isSubXact)
guc_dirty = false;
}
@ -2810,7 +3012,7 @@ set_config_option(const char *name, const char *value,
}
/*
* Should we set reset/session values? (If so, the behavior is not
* Should we set reset/stacked values? (If so, the behavior is not
* transactional.)
*/
makeDefault = changeVal && (source <= PGC_S_OVERRIDE) && (value != NULL);
@ -2820,7 +3022,7 @@ set_config_option(const char *name, const char *value,
* However, if changeVal is false then plow ahead anyway since we are
* trying to find out if the value is potentially good, not actually
* use it. Also keep going if makeDefault is true, since we may want
* to set the reset/session values even if we can't set the variable
* to set the reset/stacked values even if we can't set the variable
* itself.
*/
if (record->source > source)
@ -2901,6 +3103,9 @@ set_config_option(const char *name, const char *value,
if (changeVal || makeDefault)
{
/* Save old value to support transaction abort */
if (!makeDefault)
push_old_value(&conf->gen);
if (changeVal)
{
*conf->variable = newval;
@ -2908,15 +3113,20 @@ set_config_option(const char *name, const char *value,
}
if (makeDefault)
{
GucStack *stack;
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
if (conf->gen.session_source <= source)
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
conf->session_val = newval;
conf->gen.session_source = source;
if (stack->source <= source)
{
stack->value.boolval = newval;
stack->source = source;
}
}
}
else if (isLocal)
@ -3006,6 +3216,9 @@ set_config_option(const char *name, const char *value,
if (changeVal || makeDefault)
{
/* Save old value to support transaction abort */
if (!makeDefault)
push_old_value(&conf->gen);
if (changeVal)
{
*conf->variable = newval;
@ -3013,15 +3226,20 @@ set_config_option(const char *name, const char *value,
}
if (makeDefault)
{
GucStack *stack;
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
if (conf->gen.session_source <= source)
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
conf->session_val = newval;
conf->gen.session_source = source;
if (stack->source <= source)
{
stack->value.intval = newval;
stack->source = source;
}
}
}
else if (isLocal)
@ -3101,6 +3319,9 @@ set_config_option(const char *name, const char *value,
if (changeVal || makeDefault)
{
/* Save old value to support transaction abort */
if (!makeDefault)
push_old_value(&conf->gen);
if (changeVal)
{
*conf->variable = newval;
@ -3108,15 +3329,20 @@ set_config_option(const char *name, const char *value,
}
if (makeDefault)
{
GucStack *stack;
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
if (conf->gen.session_source <= source)
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
conf->session_val = newval;
conf->gen.session_source = source;
if (stack->source <= source)
{
stack->value.realval = newval;
stack->source = source;
}
}
}
else if (isLocal)
@ -3261,27 +3487,34 @@ set_config_option(const char *name, const char *value,
if (changeVal || makeDefault)
{
/* Save old value to support transaction abort */
if (!makeDefault)
push_old_value(&conf->gen);
if (changeVal)
{
SET_STRING_VARIABLE(conf, newval);
set_string_field(conf, conf->variable, newval);
conf->gen.source = source;
}
if (makeDefault)
{
GucStack *stack;
if (conf->gen.reset_source <= source)
{
SET_STRING_RESET_VAL(conf, newval);
set_string_field(conf, &conf->reset_val, newval);
conf->gen.reset_source = source;
}
if (conf->gen.session_source <= source)
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
SET_STRING_SESSION_VAL(conf, newval);
conf->gen.session_source = source;
if (stack->source <= source)
{
set_string_field(conf, &stack->value.stringval,
newval);
stack->source = source;
}
}
/* Perhaps we didn't install newval anywhere */
if (newval != *conf->variable &&
newval != conf->session_val &&
newval != conf->reset_val)
if (!string_field_used(conf, newval))
free(newval);
}
else if (isLocal)
@ -3291,7 +3524,7 @@ set_config_option(const char *name, const char *value,
}
else
{
SET_STRING_TENTATIVE_VAL(conf, newval);
set_string_field(conf, &conf->tentative_val, newval);
conf->gen.tentative_source = source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
guc_dirty = true;
@ -3608,44 +3841,36 @@ define_custom_variable(struct config_generic* variable)
/* This better be a placeholder
*/
if(((*res)->flags & GUC_CUSTOM_PLACEHOLDER) == 0)
{
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("attempt to redefine parameter \"%s\"", name)));
}
pHolder = (struct config_string*)*res;
Assert((*res)->vartype == PGC_STRING);
pHolder = (struct config_string*) *res;
/* We have the same name, no sorting is necessary.
*/
/* We have the same name, no sorting is necessary */
*res = variable;
value = *pHolder->variable;
/* Assign the variable stored in the placeholder to the real
* variable.
/*
* Assign the string value stored in the placeholder to the real variable.
*
* XXX this is not really good enough --- it should be a nontransactional
* assignment, since we don't want it to roll back if the current xact
* fails later.
*/
set_config_option(name, value,
pHolder->gen.context, pHolder->gen.source,
false, true);
/* Free up stuff occupied by the placeholder variable
/*
* Free up as much as we conveniently can of the placeholder structure
* (this neglects any stack items...)
*/
if(value != NULL)
free((void*)value);
if(pHolder->reset_val != NULL && pHolder->reset_val != value)
free(pHolder->reset_val);
if(pHolder->session_val != NULL
&& pHolder->session_val != value
&& pHolder->session_val != pHolder->reset_val)
free(pHolder->session_val);
if(pHolder->tentative_val != NULL
&& pHolder->tentative_val != value
&& pHolder->tentative_val != pHolder->reset_val
&& pHolder->tentative_val != pHolder->session_val)
free(pHolder->tentative_val);
set_string_field(pHolder, pHolder->variable, NULL);
set_string_field(pHolder, &pHolder->reset_val, NULL);
set_string_field(pHolder, &pHolder->tentative_val, NULL);
free(pHolder);
}
@ -3754,7 +3979,7 @@ void DefineCustomStringVariable(
define_custom_variable(&var->gen);
}
extern void EmittWarningsOnPlaceholders(const char* className)
extern void EmitWarningsOnPlaceholders(const char* className)
{
struct config_generic** vars = guc_variables;
struct config_generic** last = vars + num_guc_variables;
@ -5133,5 +5358,14 @@ assign_log_stats(bool newval, bool doit, GucSource source)
return true;
}
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;
}
#include "guc-file.c"

View File

@ -1,4 +1,4 @@
$PostgreSQL: pgsql/src/backend/utils/mmgr/README,v 1.6 2004/06/05 19:48:09 tgl Exp $
$PostgreSQL: pgsql/src/backend/utils/mmgr/README,v 1.7 2004/07/01 00:51:29 tgl Exp $
Notes about memory allocation redesign
--------------------------------------
@ -90,7 +90,7 @@ context managers as discussed below.
We could even consider getting rid of CurrentMemoryContext entirely,
instead requiring the target memory context for allocation to be specified
explicitly. But I think that would be too much notational overhead ---
we'd have to pass an apppropriate memory context to called routines in
we'd have to pass an appropriate memory context to called routines in
many places. For example, the copyObject routines would need to be passed
a context, as would function execution routines that return a
pass-by-reference datatype. And what of routines that temporarily
@ -176,15 +176,30 @@ is kept separate from per-transaction and per-portal contexts because a
query string might need to live either a longer or shorter time than any
single transaction or portal.
TopTransactionContext --- this holds everything that lives until end of
transaction (longer than one statement within a transaction!). An example
of what has to be here is the list of pending NOTIFY messages to be sent
at xact commit. This context will be reset, and all its children deleted,
at conclusion of each transaction cycle. Note: this context is NOT
cleared immediately upon error; its contents will survive until the
transaction block is exited by COMMIT/ROLLBACK.
(If we ever implement nested transactions, TopTransactionContext may need
to be split into a true "top" pointer and a "current transaction" pointer.)
TopTransactionContext --- this holds everything that lives until end of the
top-level transaction. This context will be reset, and all its children
deleted, at conclusion of each top-level transaction cycle. In most cases
you don't want to allocate stuff directly here, but in CurTransactionContext;
what does belong here is control information that exists explicitly to manage
status across multiple subtransactions. Note: this context is NOT cleared
immediately upon error; its contents will survive until the transaction block
is exited by COMMIT/ROLLBACK.
CurTransactionContext --- this holds data that has to survive until the end
of the current transaction, and in particular will be needed at top-level
transaction commit. When we are in a top-level transaction this is the same
as TopTransactionContext, but in subtransactions it points to a child context.
It is important to understand that if a subtransaction aborts, its
CurTransactionContext is thrown away after finishing the abort processing;
but a committed subtransaction's CurTransactionContext is kept until top-level
commit (unless of course one of the intermediate levels of subtransaction
aborts). This ensures that we do not keep data from a failed subtransaction
longer than necessary. Because of this behavior, you must be careful to clean
up properly during subtransaction abort --- the subtransaction's state must be
delinked from any pointers or lists kept in upper transactions, or you will
have dangling pointers leading to a crash at top-level commit. An example of
data kept here is pending NOTIFY messages, which are sent at top-level commit,
but only if the generating subtransaction did not abort.
QueryContext --- this is not actually a separate context, but a global
variable pointing to the context that holds the current command's parse

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.45 2004/06/05 19:48:09 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.46 2004/07/01 00:51:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -45,6 +45,7 @@ MemoryContext PostmasterContext = NULL;
MemoryContext CacheMemoryContext = NULL;
MemoryContext MessageContext = NULL;
MemoryContext TopTransactionContext = NULL;
MemoryContext CurTransactionContext = NULL;
/* These two are transient links to contexts owned by other objects: */
MemoryContext QueryContext = NULL;

View File

@ -12,7 +12,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.65 2004/05/30 23:40:39 neilc Exp $
* $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.66 2004/07/01 00:51:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -511,3 +511,94 @@ AtCleanup_Portals(void)
PortalDrop(portal, true);
}
}
/*
* Pre-subcommit processing for portals.
*
* Reassign the portals created in the current subtransaction to the parent
* transaction. (XXX perhaps we should reassign only holdable cursors,
* and drop the rest?)
*/
void
AtSubCommit_Portals(TransactionId parentXid)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
TransactionId curXid = GetCurrentTransactionId();
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
{
Portal portal = hentry->portal;
if (portal->createXact == curXid)
portal->createXact = parentXid;
}
}
/*
* Subtransaction abort handling for portals.
*
* Deactivate all portals created during the failed subtransaction.
* Note that per AtSubCommit_Portals, this will catch portals created
* in descendants of the subtransaction too.
*/
void
AtSubAbort_Portals(void)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
TransactionId curXid = GetCurrentTransactionId();
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
{
Portal portal = hentry->portal;
if (portal->createXact != curXid)
continue;
portal->portalActive = false;
/* let portalcmds.c clean up the state it knows about */
if (PointerIsValid(portal->cleanup))
{
(*portal->cleanup) (portal, true);
portal->cleanup = NULL;
}
}
}
/*
* Post-subabort cleanup for portals.
*
* Drop all portals created in the finishing subtransaction and all
* its descendants.
*/
void
AtSubCleanup_Portals(void)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
TransactionId curXid = GetCurrentTransactionId();
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
{
Portal portal = hentry->portal;
if (portal->createXact != curXid)
continue;
/*
* Let's just make sure no one's active...
*/
portal->portalActive = false;
/* Zap it with prejudice. */
PortalDrop(portal, true);
}
}

View File

@ -16,13 +16,14 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.72 2003/11/29 19:52:04 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.73 2004/07/01 00:51:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/subtrans.h"
#include "storage/sinval.h"
#include "utils/tqual.h"
@ -115,6 +116,10 @@ HeapTupleSatisfiesItself(HeapTupleHeader tuple)
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
/* deleting subtransaction aborted */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
return true;
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
if (tuple->t_infomask & HEAP_MARKED_FOR_UPDATE)
@ -261,6 +266,10 @@ HeapTupleSatisfiesNow(HeapTupleHeader tuple)
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
/* deleting subtransaction aborted */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
return true;
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
if (tuple->t_infomask & HEAP_MARKED_FOR_UPDATE)
@ -441,6 +450,10 @@ HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid)
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HeapTupleMayBeUpdated;
/* deleting subtransaction aborted */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
return HeapTupleMayBeUpdated;
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
if (tuple->t_infomask & HEAP_MARKED_FOR_UPDATE)
@ -575,6 +588,10 @@ HeapTupleSatisfiesDirty(HeapTupleHeader tuple)
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
/* deleting subtransaction aborted */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
return true;
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
if (tuple->t_infomask & HEAP_MARKED_FOR_UPDATE)
@ -712,6 +729,11 @@ HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple, Snapshot snapshot)
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
/* deleting subtransaction aborted */
/* FIXME -- is this correct w.r.t. the cmax of the tuple? */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
return true;
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
if (tuple->t_infomask & HEAP_MARKED_FOR_UPDATE)
@ -747,7 +769,7 @@ HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple, Snapshot snapshot)
for (i = 0; i < snapshot->xcnt; i++)
{
if (TransactionIdEquals(HeapTupleHeaderGetXmin(tuple),
if (SubTransXidsHaveCommonAncestor(HeapTupleHeaderGetXmin(tuple),
snapshot->xip[i]))
return false;
}
@ -792,7 +814,7 @@ HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple, Snapshot snapshot)
return true;
for (i = 0; i < snapshot->xcnt; i++)
{
if (TransactionIdEquals(HeapTupleHeaderGetXmax(tuple), snapshot->xip[i]))
if (SubTransXidsHaveCommonAncestor(HeapTupleHeaderGetXmax(tuple), snapshot->xip[i]))
return true;
}
}
@ -868,8 +890,8 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin)
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HEAPTUPLE_INSERT_IN_PROGRESS;
Assert(HeapTupleHeaderGetXmin(tuple) ==
HeapTupleHeaderGetXmax(tuple));
Assert(SubTransXidsHaveCommonAncestor(HeapTupleHeaderGetXmin(tuple),
HeapTupleHeaderGetXmax(tuple)));
if (tuple->t_infomask & HEAP_MARKED_FOR_UPDATE)
return HEAPTUPLE_INSERT_IN_PROGRESS;
/* inserted and then deleted by same xact */
@ -943,7 +965,7 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin)
* Deleter committed, but check special cases.
*/
if (TransactionIdEquals(HeapTupleHeaderGetXmin(tuple),
if (SubTransXidsHaveCommonAncestor(HeapTupleHeaderGetXmin(tuple),
HeapTupleHeaderGetXmax(tuple)))
{
/*

View File

@ -39,7 +39,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
* Portions taken from FreeBSD.
*
* $PostgreSQL: pgsql/src/bin/initdb/initdb.c,v 1.40 2004/06/24 19:26:59 tgl Exp $
* $PostgreSQL: pgsql/src/bin/initdb/initdb.c,v 1.41 2004/07/01 00:51:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1828,7 +1828,7 @@ main(int argc, char *argv[])
char *pgdenv; /* PGDATA value got from sent to
* environment */
char *subdirs[] =
{"global", "pg_xlog", "pg_clog", "base", "base/1", "pg_tblspc"};
{"global", "pg_xlog", "pg_clog", "pg_subtrans", "base", "base/1", "pg_tblspc"};
progname = get_progname(argv[0]);
set_pglocale_pgservice(argv[0], "initdb");

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/clog.h,v 1.8 2003/11/29 22:40:55 pgsql Exp $
* $PostgreSQL: pgsql/src/include/access/clog.h,v 1.9 2004/07/01 00:51:38 tgl Exp $
*/
#ifndef CLOG_H
#define CLOG_H
@ -16,13 +16,16 @@
/*
* Possible transaction statuses --- note that all-zeroes is the initial
* state.
*
* A "subcommitted" transaction is a committed subtransaction whose parent
* hasn't committed or aborted yet.
*/
typedef int XidStatus;
#define TRANSACTION_STATUS_IN_PROGRESS 0x00
#define TRANSACTION_STATUS_COMMITTED 0x01
#define TRANSACTION_STATUS_ABORTED 0x02
/* 0x03 is available without changing commit log space allocation */
#define TRANSACTION_STATUS_SUB_COMMITTED 0x03
/* exported because lwlock.c needs it */
#define NUM_CLOG_BUFFERS 8
@ -39,12 +42,6 @@ extern void ShutdownCLOG(void);
extern void CheckPointCLOG(void);
extern void ExtendCLOG(TransactionId newestXact);
extern void TruncateCLOG(TransactionId oldestXact);
/* XLOG stuff */
#define CLOG_ZEROPAGE 0x00
extern void clog_redo(XLogRecPtr lsn, XLogRecord *record);
extern void clog_undo(XLogRecPtr lsn, XLogRecord *record);
extern void clog_desc(char *buf, uint8 xl_info, char *rec);
extern void clog_zeropage_redo(int pageno);
#endif /* CLOG_H */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/gistscan.h,v 1.22 2003/11/29 22:40:55 pgsql Exp $
* $PostgreSQL: pgsql/src/include/access/gistscan.h,v 1.23 2004/07/01 00:51:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -23,5 +23,6 @@ extern Datum gistrestrpos(PG_FUNCTION_ARGS);
extern Datum gistendscan(PG_FUNCTION_ARGS);
extern void gistadjscans(Relation r, int op, BlockNumber blkno, OffsetNumber offnum);
extern void AtEOXact_gist(void);
extern void AtEOSubXact_gist(TransactionId childXid);
#endif /* GISTSCAN_H */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/hash.h,v 1.54 2003/11/29 22:40:55 pgsql Exp $
* $PostgreSQL: pgsql/src/include/access/hash.h,v 1.55 2004/07/01 00:51:38 tgl Exp $
*
* NOTES
* modeled after Margo Seltzer's hash implementation for unix.
@ -293,6 +293,7 @@ extern void _hash_regscan(IndexScanDesc scan);
extern void _hash_dropscan(IndexScanDesc scan);
extern bool _hash_has_active_scan(Relation rel, Bucket bucket);
extern void AtEOXact_hash(void);
extern void AtEOSubXact_hash(TransactionId childXid);
/* hashsearch.c */
extern bool _hash_next(IndexScanDesc scan, ScanDirection dir);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/htup.h,v 1.65 2004/04/01 21:28:45 tgl Exp $
* $PostgreSQL: pgsql/src/include/access/htup.h,v 1.66 2004/07/01 00:51:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -109,18 +109,14 @@
typedef struct HeapTupleFields
{
TransactionId t_xmin; /* inserting xact ID */
union
{
CommandId t_cmin; /* inserting command ID */
TransactionId t_xmax; /* deleting xact ID */
} t_field2;
CommandId t_cmin; /* inserting command ID */
TransactionId t_xmax; /* deleting xact ID */
union
{
CommandId t_cmax; /* deleting command ID */
TransactionId t_xvac; /* VACUUM FULL xact ID */
} t_field3;
} t_field4;
} HeapTupleFields;
typedef struct DatumTupleFields
@ -172,9 +168,7 @@ typedef HeapTupleHeaderData *HeapTupleHeader;
* attribute(s) */
#define HEAP_HASEXTENDED 0x000C /* the two above combined */
#define HEAP_HASOID 0x0010 /* has an object-id field */
/* bit 0x0020 is presently unused */
#define HEAP_XMAX_IS_XMIN 0x0040 /* created and deleted in the same
* transaction */
/* 0x0020 and 0x0040 are unused */
#define HEAP_XMAX_UNLOGGED 0x0080 /* to lock tuple for update
* without logging */
#define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */
@ -211,62 +205,47 @@ typedef HeapTupleHeaderData *HeapTupleHeader;
#define HeapTupleHeaderGetXmax(tup) \
( \
((tup)->t_infomask & HEAP_XMAX_IS_XMIN) ? \
(tup)->t_choice.t_heap.t_xmin \
: \
(tup)->t_choice.t_heap.t_field2.t_xmax \
(tup)->t_choice.t_heap.t_xmax \
)
#define HeapTupleHeaderSetXmax(tup, xid) \
do { \
TransactionId _newxid = (xid); \
if (TransactionIdEquals((tup)->t_choice.t_heap.t_xmin, _newxid)) \
(tup)->t_infomask |= HEAP_XMAX_IS_XMIN; \
else \
{ \
(tup)->t_infomask &= ~HEAP_XMAX_IS_XMIN; \
TransactionIdStore(_newxid, &(tup)->t_choice.t_heap.t_field2.t_xmax); \
} \
} while (0)
( \
TransactionIdStore((xid), &(tup)->t_choice.t_heap.t_xmax) \
)
#define HeapTupleHeaderGetCmin(tup) \
( \
(tup)->t_choice.t_heap.t_cmin \
)
#define HeapTupleHeaderSetCmin(tup, cid) \
( \
(tup)->t_choice.t_heap.t_cmin = (cid) \
)
/*
* Note: GetCmin will produce wrong answers after SetXmax has been executed
* Note: GetCmax will produce wrong answers after SetXvac has been executed
* by a transaction other than the inserting one. We could check
* HEAP_XMAX_INVALID and return FirstCommandId if it's clear, but since that
* bit will be set again if the deleting transaction aborts, there'd be no
* real gain in safety from the extra test. So, just rely on the caller not
* to trust the value unless it's meaningful.
*/
#define HeapTupleHeaderGetCmin(tup) \
( \
(tup)->t_choice.t_heap.t_field2.t_cmin \
)
#define HeapTupleHeaderSetCmin(tup, cid) \
do { \
Assert((tup)->t_infomask & HEAP_XMAX_INVALID); \
(tup)->t_choice.t_heap.t_field2.t_cmin = (cid); \
} while (0)
/*
* As with GetCmin, we can't completely ensure that GetCmax can detect whether
* a valid command ID is available, and there's little point in a partial test.
*/
#define HeapTupleHeaderGetCmax(tup) \
( \
(tup)->t_choice.t_heap.t_field3.t_cmax \
(tup)->t_choice.t_heap.t_field4.t_cmax \
)
#define HeapTupleHeaderSetCmax(tup, cid) \
do { \
Assert(!((tup)->t_infomask & HEAP_MOVED)); \
(tup)->t_choice.t_heap.t_field3.t_cmax = (cid); \
(tup)->t_choice.t_heap.t_field4.t_cmax = (cid); \
} while (0)
#define HeapTupleHeaderGetXvac(tup) \
( \
((tup)->t_infomask & HEAP_MOVED) ? \
(tup)->t_choice.t_heap.t_field3.t_xvac \
(tup)->t_choice.t_heap.t_field4.t_xvac \
: \
InvalidTransactionId \
)
@ -274,7 +253,7 @@ do { \
#define HeapTupleHeaderSetXvac(tup, xid) \
do { \
Assert((tup)->t_infomask & HEAP_MOVED); \
TransactionIdStore((xid), &(tup)->t_choice.t_heap.t_field3.t_xvac); \
TransactionIdStore((xid), &(tup)->t_choice.t_heap.t_field4.t_xvac); \
} while (0)
#define HeapTupleHeaderGetDatumLength(tup) \

View File

@ -3,7 +3,7 @@
*
* Resource managers definition
*
* $PostgreSQL: pgsql/src/include/access/rmgr.h,v 1.10 2003/11/29 22:40:55 pgsql Exp $
* $PostgreSQL: pgsql/src/include/access/rmgr.h,v 1.11 2004/07/01 00:51:38 tgl Exp $
*/
#ifndef RMGR_H
#define RMGR_H
@ -16,7 +16,7 @@ typedef uint8 RmgrId;
#define RM_XLOG_ID 0
#define RM_XACT_ID 1
#define RM_SMGR_ID 2
#define RM_CLOG_ID 3
#define RM_SLRU_ID 3
#define RM_HEAP_ID 10
#define RM_BTREE_ID 11
#define RM_HASH_ID 12

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/rtree.h,v 1.32 2003/11/29 22:40:55 pgsql Exp $
* $PostgreSQL: pgsql/src/include/access/rtree.h,v 1.33 2004/07/01 00:51:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -130,6 +130,7 @@ extern void rtree_desc(char *buf, uint8 xl_info, char *rec);
extern void rtadjscans(Relation r, int op, BlockNumber blkno,
OffsetNumber offnum);
extern void AtEOXact_rtree(void);
extern void AtEOSubXact_rtree(TransactionId childXid);
/* rtstrat.c */
extern StrategyNumber RTMapToInternalOperator(StrategyNumber strat);

View File

@ -6,11 +6,12 @@
* Portions Copyright (c) 2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/slru.h,v 1.6 2004/05/31 03:48:08 tgl Exp $
* $PostgreSQL: pgsql/src/include/access/slru.h,v 1.7 2004/07/01 00:51:38 tgl Exp $
*/
#ifndef SLRU_H
#define SLRU_H
#include "access/xlog.h"
#include "storage/lwlock.h"
@ -56,4 +57,12 @@ extern void SimpleLruSetLatestPage(SlruCtl ctl, int pageno);
extern void SimpleLruFlush(SlruCtl ctl, bool checkpoint);
extern void SimpleLruTruncate(SlruCtl ctl, int cutoffPage);
/* XLOG stuff */
#define CLOG_ZEROPAGE 0x00
#define SUBTRANS_ZEROPAGE 0x10
extern void slru_redo(XLogRecPtr lsn, XLogRecord *record);
extern void slru_undo(XLogRecPtr lsn, XLogRecord *record);
extern void slru_desc(char *buf, uint8 xl_info, char *rec);
#endif /* SLRU_H */

View File

@ -0,0 +1,35 @@
/*
* subtrans.h
*
* PostgreSQL subtrans-log manager
*
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/subtrans.h,v 1.1 2004/07/01 00:51:38 tgl Exp $
*/
#ifndef SUBTRANS_H
#define SUBTRANS_H
#include "access/xlog.h"
/* exported because lwlock.c needs it */
/* cannot be different from NUM_CLOG_BUFFERS without slru.c changes */
#define NUM_SUBTRANS_BUFFERS NUM_CLOG_BUFFERS
extern void SubTransSetParent(TransactionId xid, TransactionId parent);
extern TransactionId SubTransGetParent(TransactionId xid);
extern TransactionId SubTransGetTopmostTransaction(TransactionId xid);
extern bool SubTransXidsHaveCommonAncestor(TransactionId xid1, TransactionId xid2);
extern int SUBTRANSShmemSize(void);
extern void SUBTRANSShmemInit(void);
extern void BootStrapSUBTRANS(void);
extern void StartupSUBTRANS(void);
extern void ShutdownSUBTRANS(void);
extern void CheckPointSUBTRANS(void);
extern void ExtendSUBTRANS(TransactionId newestXact);
extern void TruncateSUBTRANS(TransactionId oldestXact);
extern void subtrans_zeropage_redo(int pageno);
#endif /* SUBTRANS_H */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/transam.h,v 1.48 2003/11/29 22:40:55 pgsql Exp $
* $PostgreSQL: pgsql/src/include/access/transam.h,v 1.49 2004/07/01 00:51:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -107,13 +107,16 @@ extern bool TransactionIdDidCommit(TransactionId transactionId);
extern bool TransactionIdDidAbort(TransactionId transactionId);
extern void TransactionIdCommit(TransactionId transactionId);
extern void TransactionIdAbort(TransactionId transactionId);
extern void TransactionIdSubCommit(TransactionId transactionId);
extern void TransactionIdCommitTree(int nxids, TransactionId *xids);
extern void TransactionIdAbortTree(int nxids, TransactionId *xids);
extern bool TransactionIdPrecedes(TransactionId id1, TransactionId id2);
extern bool TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2);
extern bool TransactionIdFollows(TransactionId id1, TransactionId id2);
extern bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2);
/* in transam/varsup.c */
extern TransactionId GetNewTransactionId(void);
extern TransactionId GetNewTransactionId(bool isSubXact);
extern TransactionId ReadNewTransactionId(void);
extern Oid GetNewObjectId(void);
extern void CheckMaxObjectId(Oid assigned_oid);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/xact.h,v 1.63 2004/05/22 23:14:38 tgl Exp $
* $PostgreSQL: pgsql/src/include/access/xact.h,v 1.64 2004/07/01 00:51:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -63,7 +63,15 @@ typedef enum TBlockState
TBLOCK_INPROGRESS,
TBLOCK_END,
TBLOCK_ABORT,
TBLOCK_ENDABORT
TBLOCK_ENDABORT,
TBLOCK_SUBBEGIN,
TBLOCK_SUBBEGINABORT,
TBLOCK_SUBINPROGRESS,
TBLOCK_SUBEND,
TBLOCK_SUBABORT,
TBLOCK_SUBENDABORT_OK,
TBLOCK_SUBENDABORT_ERROR
} TBlockState;
/*
@ -76,12 +84,15 @@ typedef void (*EOXactCallback) (bool isCommit, void *arg);
*/
typedef struct TransactionStateData
{
TransactionId transactionIdData;
CommandId commandId;
AbsoluteTime startTime;
int startTimeUsec;
TransState state;
TBlockState blockState;
TransactionId transactionIdData; /* my XID */
CommandId commandId; /* current CID */
TransState state; /* low-level state */
TBlockState blockState; /* high-level state */
int nestingLevel; /* nest depth */
MemoryContext curTransactionContext; /* my xact-lifetime context */
List *childXids; /* subcommitted child XIDs */
AclId currentUser; /* subxact start current_user */
struct TransactionStateData *parent; /* back link to parent */
} TransactionStateData;
typedef TransactionStateData *TransactionState;
@ -102,9 +113,11 @@ typedef TransactionStateData *TransactionState;
typedef struct xl_xact_commit
{
time_t xtime;
int nrels; /* number of RelFileNodes */
int nsubxacts; /* number of subtransaction XIDs */
/* Array of RelFileNode(s) to drop at commit */
/* The XLOG record length determines how many there are */
RelFileNode xnodes[1]; /* VARIABLE LENGTH ARRAY */
/* ARRAY OF COMMITTED SUBTRANSACTION XIDs FOLLOWS */
} xl_xact_commit;
#define MinSizeOfXactCommit offsetof(xl_xact_commit, xnodes)
@ -112,9 +125,11 @@ typedef struct xl_xact_commit
typedef struct xl_xact_abort
{
time_t xtime;
int nrels; /* number of RelFileNodes */
int nsubxacts; /* number of subtransaction XIDs */
/* Array of RelFileNode(s) to drop at abort */
/* The XLOG record length determines how many there are */
RelFileNode xnodes[1]; /* VARIABLE LENGTH ARRAY */
/* ARRAY OF ABORTED SUBTRANSACTION XIDs FOLLOWS */
} xl_xact_abort;
#define MinSizeOfXactAbort offsetof(xl_xact_abort, xnodes)
@ -126,18 +141,20 @@ typedef struct xl_xact_abort
*/
extern bool IsTransactionState(void);
extern bool IsAbortedTransactionBlockState(void);
extern TransactionId GetTopTransactionId(void);
extern TransactionId GetCurrentTransactionId(void);
extern CommandId GetCurrentCommandId(void);
extern AbsoluteTime GetCurrentTransactionStartTime(void);
extern AbsoluteTime GetCurrentTransactionStartTimeUsec(int *usec);
extern int GetCurrentTransactionNestLevel(void);
extern bool TransactionIdIsCurrentTransactionId(TransactionId xid);
extern bool CommandIdIsCurrentCommandId(CommandId cid);
extern void CommandCounterIncrement(void);
extern void StartTransactionCommand(void);
extern void CommitTransactionCommand(void);
extern void AbortCurrentTransaction(void);
extern void BeginTransactionBlock(void);
extern void EndTransactionBlock(void);
extern bool IsSubTransaction(void);
extern bool IsTransactionBlock(void);
extern bool IsTransactionOrTransactionBlock(void);
extern char TransactionBlockStatusCode(void);
@ -151,6 +168,8 @@ extern void UnregisterEOXactCallback(EOXactCallback callback, void *arg);
extern void RecordTransactionCommit(void);
extern int xactGetCommittedChildren(TransactionId **ptr, bool metoo);
extern void XactPushRollback(void (*func) (void *), void *data);
extern void XactPopRollback(void);

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/xlog.h,v 1.51 2004/05/29 22:48:22 tgl Exp $
* $PostgreSQL: pgsql/src/include/access/xlog.h,v 1.52 2004/07/01 00:51:38 tgl Exp $
*/
#ifndef XLOG_H
#define XLOG_H
@ -111,7 +111,7 @@ typedef struct XLogContRecord
/*
* Each page of XLOG file has a header like this:
*/
#define XLOG_PAGE_MAGIC 0xD05A /* can be used as WAL version indicator */
#define XLOG_PAGE_MAGIC 0xD05B /* can be used as WAL version indicator */
typedef struct XLogPageHeaderData
{

View File

@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.240 2004/06/25 17:20:28 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.241 2004/07/01 00:51:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200406251
#define CATALOG_VERSION_NO 200406261
#endif

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/async.h,v 1.24 2004/05/23 03:50:45 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/async.h,v 1.25 2004/07/01 00:51:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -23,6 +23,9 @@ extern void Async_Unlisten(char *relname, int pid);
/* perform (or cancel) outbound notify processing at transaction commit */
extern void AtCommit_Notify(void);
extern void AtAbort_Notify(void);
extern void AtSubStart_Notify(void);
extern void AtSubCommit_Notify(void);
extern void AtSubAbort_Notify(void);
/* signal handler for inbound notifies (SIGUSR2) */
extern void NotifyInterruptHandler(SIGNAL_ARGS);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.16 2004/05/05 04:48:47 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.17 2004/07/01 00:51:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -42,6 +42,9 @@ extern void register_on_commit_action(Oid relid, OnCommitAction action);
extern void remove_on_commit_action(Oid relid);
extern void PreCommit_on_commit_actions(void);
extern void AtEOXact_on_commit_actions(bool isCommit);
extern void AtEOXact_on_commit_actions(bool isCommit, TransactionId xid);
extern void AtEOSubXact_on_commit_actions(bool isCommit,
TransactionId childXid,
TransactionId parentXid);
#endif /* TABLECMDS_H */

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.45 2003/11/29 22:40:59 pgsql Exp $
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.46 2004/07/01 00:51:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -151,44 +151,12 @@ extern void ExecARUpdateTriggers(EState *estate,
ItemPointer tupleid,
HeapTuple newtuple);
/*
* Deferred trigger stuff
*/
typedef struct DeferredTriggerStatusData
{
Oid dts_tgoid;
bool dts_tgisdeferred;
} DeferredTriggerStatusData;
typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
typedef struct DeferredTriggerEventItem
{
Oid dti_tgoid;
int32 dti_state;
} DeferredTriggerEventItem;
typedef struct DeferredTriggerEventData *DeferredTriggerEvent;
typedef struct DeferredTriggerEventData
{
DeferredTriggerEvent dte_next; /* list link */
int32 dte_event;
Oid dte_relid;
ItemPointerData dte_oldctid;
ItemPointerData dte_newctid;
int32 dte_n_items;
/* dte_item is actually a variable-size array, of length dte_n_items */
DeferredTriggerEventItem dte_item[1];
} DeferredTriggerEventData;
extern void DeferredTriggerInit(void);
extern void DeferredTriggerBeginXact(void);
extern void DeferredTriggerEndQuery(void);
extern void DeferredTriggerEndXact(void);
extern void DeferredTriggerAbortXact(void);
extern void DeferredTriggerBeginSubXact(void);
extern void DeferredTriggerEndSubXact(bool isCommit);
extern void DeferredTriggerSetState(ConstraintsSetStmt *stmt);

View File

@ -2,7 +2,7 @@
*
* spi.h
*
* $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.44 2004/04/01 21:28:46 tgl Exp $
* $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.45 2004/07/01 00:51:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -119,5 +119,6 @@ extern void SPI_cursor_move(Portal portal, bool forward, int count);
extern void SPI_cursor_close(Portal portal);
extern void AtEOXact_SPI(bool isCommit);
extern void AtEOSubXact_SPI(bool isCommit, TransactionId childXid);
#endif /* SPI_H */

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.18 2004/03/21 22:29:11 tgl Exp $
* $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.19 2004/07/01 00:51:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -23,6 +23,7 @@ typedef struct
MemoryContext procCxt; /* procedure context */
MemoryContext execCxt; /* executor context */
MemoryContext savedcxt;
TransactionId connectXid; /* Xid of connecting transaction */
} _SPI_connection;
typedef struct

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/storage/bufmgr.h,v 1.82 2004/05/31 19:24:05 tgl Exp $
* $PostgreSQL: pgsql/src/include/storage/bufmgr.h,v 1.83 2004/07/01 00:51:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -148,6 +148,8 @@ extern void InitBufferPoolAccess(void);
extern char *ShowBufferUsage(void);
extern void ResetBufferUsage(void);
extern void AtEOXact_Buffers(bool isCommit);
extern void AtSubStart_Buffers(void);
extern void AtEOSubXact_Buffers(bool isCommit);
extern void FlushBufferPool(void);
extern BlockNumber BufferGetBlockNumber(Buffer buffer);
extern BlockNumber RelationGetNumberOfBlocks(Relation relation);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/storage/bufpage.h,v 1.58 2004/06/05 17:42:46 tgl Exp $
* $PostgreSQL: pgsql/src/include/storage/bufpage.h,v 1.59 2004/07/01 00:51:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -126,10 +126,11 @@ typedef struct PageHeaderData
typedef PageHeaderData *PageHeader;
/*
* Page layout version number 0 is for pre-7.3 Postgres releases. The
* current version number is 1, denoting a new HeapTupleHeader layout.
* Page layout version number 0 is for pre-7.3 Postgres releases.
* Releases 7.3 and 7.4 use 1, denoting a new HeapTupleHeader layout.
* Release 7.5 changed the HeapTupleHeader layout again.
*/
#define PG_PAGE_LAYOUT_VERSION 1
#define PG_PAGE_LAYOUT_VERSION 2
/* ----------------------------------------------------------------

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/storage/lock.h,v 1.77 2004/05/28 05:13:29 tgl Exp $
* $PostgreSQL: pgsql/src/include/storage/lock.h,v 1.78 2004/07/01 00:51:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -26,6 +26,14 @@ typedef struct PROC_QUEUE
int size; /* number of entries in list */
} PROC_QUEUE;
/* Release options for LockReleaseAll */
typedef enum
{
ReleaseAll, /* All my locks */
ReleaseAllExceptSession, /* All except session locks (Xid = 0) */
ReleaseGivenXids /* Only locks with Xids in given array */
} LockReleaseWhich;
/* struct PGPROC is declared in storage/proc.h, but must forward-reference it */
typedef struct PGPROC PGPROC;
@ -165,11 +173,12 @@ typedef struct LOCK
*
* There are two possible kinds of proclock tags: a transaction (identified
* both by the PGPROC of the backend running it, and the xact's own ID) and
* a session (identified by backend PGPROC, with xid = InvalidTransactionId).
* a session (identified by backend PGPROC, with XID = InvalidTransactionId).
*
* Currently, session proclocks are used for user locks and for cross-xact
* locks obtained for VACUUM. We assume that a session lock never conflicts
* with per-transaction locks obtained by the same backend.
* locks obtained for VACUUM. Note that a single backend can hold locks
* under several different XIDs at once (including session locks). We treat
* such locks as never conflicting (a backend can never block itself).
*
* The holding[] array counts the granted locks (of each type) represented
* by this proclock. Note that there will be a proclock object, possibly with
@ -177,11 +186,11 @@ typedef struct LOCK
* Otherwise, proclock objects whose counts have gone to zero are recycled
* as soon as convenient.
*
* Each PROCLOCK object is linked into lists for both the associated LOCK object
* and the owning PGPROC object. Note that the PROCLOCK is entered into these
* lists as soon as it is created, even if no lock has yet been granted.
* A PGPROC that is waiting for a lock to be granted will also be linked into
* the lock's waitProcs queue.
* Each PROCLOCK object is linked into lists for both the associated LOCK
* object and the owning PGPROC object. Note that the PROCLOCK is entered
* into these lists as soon as it is created, even if no lock has yet been
* granted. A PGPROC that is waiting for a lock to be granted will also be
* linked into the lock's waitProcs queue.
*/
typedef struct PROCLOCKTAG
{
@ -239,7 +248,7 @@ extern bool LockAcquire(LOCKMETHODID lockmethodid, LOCKTAG *locktag,
extern bool LockRelease(LOCKMETHODID lockmethodid, LOCKTAG *locktag,
TransactionId xid, LOCKMODE lockmode);
extern bool LockReleaseAll(LOCKMETHODID lockmethodid, PGPROC *proc,
bool allxids, TransactionId xid);
LockReleaseWhich which, int nxids, TransactionId *xids);
extern int LockCheckConflicts(LockMethod lockMethodTable,
LOCKMODE lockmode,
LOCK *lock, PROCLOCK *proclock, PGPROC *proc,

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/storage/proc.h,v 1.67 2003/12/01 21:59:25 momjian Exp $
* $PostgreSQL: pgsql/src/include/storage/proc.h,v 1.68 2004/07/01 00:51:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -103,7 +103,8 @@ extern int ProcGlobalSemas(int maxBackends);
extern void InitProcGlobal(int maxBackends);
extern void InitProcess(void);
extern void InitDummyProcess(int proctype);
extern void ProcReleaseLocks(bool isCommit);
extern void ProcReleaseLocks(LockReleaseWhich which,
int nxids, TransactionId *xids);
extern void ProcQueueInit(PROC_QUEUE *queue);
extern int ProcSleep(LockMethod lockMethodTable, LOCKMODE lockmode,

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/storage/smgr.h,v 1.44 2004/06/02 17:28:18 tgl Exp $
* $PostgreSQL: pgsql/src/include/storage/smgr.h,v 1.45 2004/07/01 00:51:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -66,6 +66,9 @@ extern BlockNumber smgrtruncate(SMgrRelation reln, BlockNumber nblocks,
extern void smgrimmedsync(SMgrRelation reln);
extern void smgrDoPendingDeletes(bool isCommit);
extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
extern void AtSubStart_smgr(void);
extern void AtSubCommit_smgr(void);
extern void AtSubAbort_smgr(void);
extern void smgrcommit(void);
extern void smgrabort(void);
extern void smgrsync(void);

View File

@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/catcache.h,v 1.48 2003/11/29 22:41:15 pgsql Exp $
* $PostgreSQL: pgsql/src/include/utils/catcache.h,v 1.49 2004/07/01 00:51:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -101,6 +101,9 @@ typedef struct catctup
* and negative entries is identical.
*/
int refcount; /* number of active references */
int *prev_refcount; /* refcounts for upper subtransactions */
int numpushes; /* number of used refcounts in the array */
int numalloc; /* allocated size of array */
bool dead; /* dead but not yet removed? */
bool negative; /* negative cache entry? */
uint32 hash_value; /* hash value for this tuple's keys */
@ -139,6 +142,9 @@ typedef struct catclist
*/
Dlelem cache_elem; /* list member of per-catcache list */
int refcount; /* number of active references */
int *prev_refcount; /* refcounts for upper subtransactions */
int numpushes; /* number of used refcounts in the array */
int numalloc; /* allocated size of array */
bool dead; /* dead but not yet removed? */
bool ordered; /* members listed in index order? */
short nkeys; /* number of lookup keys specified */
@ -163,6 +169,8 @@ extern DLLIMPORT MemoryContext CacheMemoryContext;
extern void CreateCacheMemoryContext(void);
extern void AtEOXact_CatCache(bool isCommit);
extern void AtSubStart_CatCache(void);
extern void AtEOSubXact_CatCache(bool isCommit);
extern CatCache *InitCatCache(int id, const char *relname, const char *indname,
int reloidattr,

View File

@ -7,7 +7,7 @@
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
* Written by Peter Eisentraut <peter_e@gmx.net>.
*
* $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.47 2004/05/28 05:13:32 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.48 2004/07/01 00:51:44 tgl Exp $
*--------------------------------------------------------------------
*/
#ifndef GUC_H
@ -175,14 +175,14 @@ extern void DefineCustomStringVariable(
GucStringAssignHook assign_hook,
GucShowHook show_hook);
extern void EmittWarningsOnPlaceholders(const char* className);
extern void EmitWarningsOnPlaceholders(const char* className);
extern const char *GetConfigOption(const char *name);
extern const char *GetConfigOptionResetString(const char *name);
extern void ProcessConfigFile(GucContext context);
extern void InitializeGUCOptions(void);
extern void ResetAllOptions(void);
extern void AtEOXact_GUC(bool isCommit);
extern void AtEOXact_GUC(bool isCommit, bool isSubXact);
extern void BeginReportingGUCOptions(void);
extern void ParseLongOption(const char *string, char **name, char **value);
extern bool set_config_option(const char *name, const char *value,

View File

@ -7,12 +7,31 @@
*
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/include/utils/guc_tables.h,v 1.11 2004/05/26 15:07:41 momjian Exp $
* $PostgreSQL: pgsql/src/include/utils/guc_tables.h,v 1.12 2004/07/01 00:51:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef GUC_TABLES
#define GUC_TABLES 1
#ifndef GUC_TABLES_H
#define GUC_TABLES_H 1
/*
* GUC supports these types of variables:
*/
enum config_type
{
PGC_BOOL,
PGC_INT,
PGC_REAL,
PGC_STRING
};
union config_var_value
{
bool boolval;
int intval;
double realval;
char *stringval;
};
/*
* Groupings to help organize all the run-time options for display
@ -56,15 +75,19 @@ enum config_group
};
/*
* GUC supports these types of variables:
* Stack entry for saving the state of a variable prior to the current
* transaction
*/
enum config_type
typedef struct guc_stack
{
PGC_BOOL,
PGC_INT,
PGC_REAL,
PGC_STRING
};
struct guc_stack *prev; /* previous stack item, if any */
int nest_level; /* nesting depth of cur transaction */
int status; /* previous status bits, see below */
GucSource tentative_source; /* source of the tentative_value */
GucSource source; /* source of the actual value */
union config_var_value tentative_val; /* previous tentative val */
union config_var_value value; /* previous actual value */
} GucStack;
/*
* Generic fields applicable to all types of variables
@ -86,9 +109,9 @@ struct config_generic
enum config_type vartype; /* type of variable (set only at startup) */
int status; /* status bits, see below */
GucSource reset_source; /* source of the reset_value */
GucSource session_source; /* source of the session_value */
GucSource tentative_source; /* source of the tentative_value */
GucSource source; /* source of the current actual value */
GucStack *stack; /* stacked outside-of-transaction states */
};
/* bit values in flags field */
@ -104,6 +127,7 @@ struct config_generic
/* bit values in status field */
#define GUC_HAVE_TENTATIVE 0x0001 /* tentative value is defined */
#define GUC_HAVE_LOCAL 0x0002 /* a SET LOCAL has been executed */
#define GUC_HAVE_STACK 0x0004 /* we have stacked prior value(s) */
/* GUC records for specific variable types */
@ -118,7 +142,6 @@ struct config_bool
GucBoolAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
bool session_val;
bool tentative_val;
};
@ -134,7 +157,6 @@ struct config_int
GucIntAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
int session_val;
int tentative_val;
};
@ -150,7 +172,6 @@ struct config_real
GucRealAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
double session_val;
double tentative_val;
};
@ -165,7 +186,6 @@ struct config_string
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
char *reset_val;
char *session_val;
char *tentative_val;
};
@ -180,4 +200,4 @@ extern struct config_generic **get_guc_variables(void);
extern void build_guc_variables(void);
#endif
#endif /* GUC_TABLES_H */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/inval.h,v 1.31 2004/05/06 16:10:57 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/inval.h,v 1.32 2004/07/01 00:51:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,9 +22,15 @@ typedef void (*CacheCallbackFunction) (Datum arg, Oid relid);
extern void AcceptInvalidationMessages(void);
extern void AtEOXactInvalidationMessages(bool isCommit);
extern void AtStart_Inval(void);
extern void CommandEndInvalidationMessages(bool isCommit);
extern void AtSubStart_Inval(void);
extern void AtEOXact_Inval(bool isCommit);
extern void AtSubEOXact_Inval(bool isCommit);
extern void CommandEndInvalidationMessages(void);
extern void CacheInvalidateHeapTuple(Relation relation, HeapTuple tuple);

View File

@ -10,7 +10,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.55 2004/06/05 19:48:09 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.56 2004/07/01 00:51:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -73,6 +73,7 @@ extern DLLIMPORT MemoryContext PostmasterContext;
extern DLLIMPORT MemoryContext CacheMemoryContext;
extern DLLIMPORT MemoryContext MessageContext;
extern DLLIMPORT MemoryContext TopTransactionContext;
extern DLLIMPORT MemoryContext CurTransactionContext;
/* These two are transient links to contexts owned by other objects: */
extern DLLIMPORT MemoryContext QueryContext;

View File

@ -39,7 +39,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.48 2003/11/29 22:41:16 pgsql Exp $
* $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.49 2004/07/01 00:51:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -167,6 +167,9 @@ extern void EnablePortalManager(void);
extern void AtCommit_Portals(void);
extern void AtAbort_Portals(void);
extern void AtCleanup_Portals(void);
extern void AtSubCommit_Portals(TransactionId parentXid);
extern void AtSubAbort_Portals(void);
extern void AtSubCleanup_Portals(void);
extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent);
extern Portal CreateNewPortal(void);
extern void PortalDrop(Portal portal, bool isError);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.74 2004/05/08 19:09:25 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.75 2004/07/01 00:51:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -110,6 +110,9 @@ typedef struct RelationData
BlockNumber rd_targblock; /* current insertion target block, or
* InvalidBlockNumber */
int rd_refcnt; /* reference count */
int *rd_prevrefcnt; /* reference count stack */
int rd_numalloc; /* stack allocated size */
int rd_numpushed; /* stack used size */
bool rd_isnew; /* rel was created in current xact */
/*

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/relcache.h,v 1.40 2004/06/18 06:14:21 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/relcache.h,v 1.41 2004/07/01 00:51:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -65,7 +65,9 @@ extern void RelationCacheInvalidateEntry(Oid relationId, RelFileNode *rnode);
extern void RelationCacheInvalidate(void);
extern void AtEOXact_RelationCache(bool commit);
extern void AtEOXact_RelationCache(bool isCommit);
extern void AtSubStart_RelationCache(void);
extern void AtEOSubXact_RelationCache(bool isCommit);
/*
* Routines to help manage rebuilding of relcache init file

View File

@ -68,3 +68,70 @@ ERROR: transaction is read-only
START TRANSACTION READ WRITE;
DROP TABLE writetest; -- ok
COMMIT;
-- Subtransactions, basic tests
-- create & drop tables
SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;
CREATE TABLE foobar (a int);
BEGIN;
CREATE TABLE foo (a int);
BEGIN;
DROP TABLE foo;
CREATE TABLE bar (a int);
ROLLBACK;
BEGIN;
CREATE TABLE baz (a int);
COMMIT;
drop TABLE foobar;
CREATE TABLE barbaz (a int);
COMMIT;
-- should exist: barbaz, baz, foo
SELECT * FROM foo; -- should be empty
a
---
(0 rows)
SELECT * FROM bar; -- shouldn't exist
ERROR: relation "bar" does not exist
SELECT * FROM barbaz; -- should be empty
a
---
(0 rows)
SELECT * FROM baz; -- should be empty
a
---
(0 rows)
-- inserts
BEGIN;
INSERT INTO foo VALUES (1);
BEGIN;
INSERT into bar VALUES (1);
ERROR: relation "bar" does not exist
ROLLBACK;
BEGIN;
INSERT into barbaz VALUES (1);
COMMIT;
BEGIN;
BEGIN;
INSERT INTO foo VALUES (2);
COMMIT;
ROLLBACK;
INSERT INTO foo VALUES (3);
COMMIT;
SELECT * FROM foo; -- should have 1 and 3
a
---
1
3
(2 rows)
SELECT * FROM barbaz; -- should have 1
a
---
1
(1 row)
DROP TABLE foo;
DROP TABLE baz;
DROP TABLE barbaz;

View File

@ -1,8 +1,18 @@
--
-- WITHOUT OID
--
CREATE TABLE wi (i INT) WITH OIDS;
CREATE TABLE wo (i INT) WITHOUT OIDS;
--
-- This test tries to verify that WITHOUT OIDS actually saves space.
-- On machines where MAXALIGN is 8, WITHOUT OIDS may or may not save any
-- space, depending on the size of the tuple header + null bitmap.
-- As of 7.5 we need a 9-bit null bitmap to force the difference to appear.
--
CREATE TABLE wi (i INT,
n1 int, n2 int, n3 int, n4 int,
n5 int, n6 int, n7 int, n8 int) WITH OIDS;
CREATE TABLE wo (i INT,
n1 int, n2 int, n3 int, n4 int,
n5 int, n6 int, n7 int, n8 int) WITHOUT OIDS;
INSERT INTO wi VALUES (1); -- 1
INSERT INTO wo SELECT i FROM wi; -- 1
INSERT INTO wo SELECT i+1 FROM wi; -- 1+1=2
@ -24,6 +34,15 @@ INSERT INTO wo SELECT i+896 FROM wi; -- 896+2448=3344
INSERT INTO wo SELECT i+3344 FROM wo; -- 3344+3344=6688
INSERT INTO wi SELECT i+2448 FROM wo; -- 2448+6688=9136
INSERT INTO wo SELECT i+6688 FROM wi WHERE i<=2448; -- 6688+2448=9136
SELECT count(oid) FROM wi;
count
-------
9136
(1 row)
-- should fail
SELECT count(oid) FROM wo;
ERROR: column "oid" does not exist
VACUUM ANALYZE wi;
VACUUM ANALYZE wo;
SELECT min(relpages) < max(relpages), min(reltuples) - max(reltuples)

View File

@ -54,3 +54,48 @@ CREATE TABLE test AS SELECT * FROM writetest; -- fail
START TRANSACTION READ WRITE;
DROP TABLE writetest; -- ok
COMMIT;
-- Subtransactions, basic tests
-- create & drop tables
SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;
CREATE TABLE foobar (a int);
BEGIN;
CREATE TABLE foo (a int);
BEGIN;
DROP TABLE foo;
CREATE TABLE bar (a int);
ROLLBACK;
BEGIN;
CREATE TABLE baz (a int);
COMMIT;
drop TABLE foobar;
CREATE TABLE barbaz (a int);
COMMIT;
-- should exist: barbaz, baz, foo
SELECT * FROM foo; -- should be empty
SELECT * FROM bar; -- shouldn't exist
SELECT * FROM barbaz; -- should be empty
SELECT * FROM baz; -- should be empty
-- inserts
BEGIN;
INSERT INTO foo VALUES (1);
BEGIN;
INSERT into bar VALUES (1);
ROLLBACK;
BEGIN;
INSERT into barbaz VALUES (1);
COMMIT;
BEGIN;
BEGIN;
INSERT INTO foo VALUES (2);
COMMIT;
ROLLBACK;
INSERT INTO foo VALUES (3);
COMMIT;
SELECT * FROM foo; -- should have 1 and 3
SELECT * FROM barbaz; -- should have 1
DROP TABLE foo;
DROP TABLE baz;
DROP TABLE barbaz;

View File

@ -2,8 +2,19 @@
-- WITHOUT OID
--
CREATE TABLE wi (i INT) WITH OIDS;
CREATE TABLE wo (i INT) WITHOUT OIDS;
--
-- This test tries to verify that WITHOUT OIDS actually saves space.
-- On machines where MAXALIGN is 8, WITHOUT OIDS may or may not save any
-- space, depending on the size of the tuple header + null bitmap.
-- As of 7.5 we need a 9-bit null bitmap to force the difference to appear.
--
CREATE TABLE wi (i INT,
n1 int, n2 int, n3 int, n4 int,
n5 int, n6 int, n7 int, n8 int) WITH OIDS;
CREATE TABLE wo (i INT,
n1 int, n2 int, n3 int, n4 int,
n5 int, n6 int, n7 int, n8 int) WITHOUT OIDS;
INSERT INTO wi VALUES (1); -- 1
INSERT INTO wo SELECT i FROM wi; -- 1
INSERT INTO wo SELECT i+1 FROM wi; -- 1+1=2
@ -25,8 +36,14 @@ INSERT INTO wo SELECT i+896 FROM wi; -- 896+2448=3344
INSERT INTO wo SELECT i+3344 FROM wo; -- 3344+3344=6688
INSERT INTO wi SELECT i+2448 FROM wo; -- 2448+6688=9136
INSERT INTO wo SELECT i+6688 FROM wi WHERE i<=2448; -- 6688+2448=9136
SELECT count(oid) FROM wi;
-- should fail
SELECT count(oid) FROM wo;
VACUUM ANALYZE wi;
VACUUM ANALYZE wo;
SELECT min(relpages) < max(relpages), min(reltuples) - max(reltuples)
FROM pg_class
WHERE relname IN ('wi', 'wo');