diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 70c255f00ce5930bcc3917e3a27b78d7840c4afc..635fc7715b4f8cf5ef18c4721d0b7da12e18c016 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1,4 +1,4 @@
-<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.31 2003/04/25 19:45:08 tgl Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.32 2003/04/26 20:22:57 tgl Exp $ -->
 
 <chapter id="protocol">
  <title>Frontend/Backend Protocol</title>
@@ -3870,6 +3870,11 @@ individual fields will typically not end with a newline, whereas the single
 string sent in the older protocol always did.
 </para>
 
+<para>
+The ReadyForQuery ('<literal>Z</>') message includes a transaction status
+indicator.
+</para>
+
 <para>
 COPY data is now encapsulated into CopyData and CopyDone messages.  There
 is a well-defined way to recover from errors during COPY.  The special
@@ -3877,7 +3882,7 @@ is a well-defined way to recover from errors during COPY.  The special
 during COPY OUT.
 (It is still recognized as a terminator during COPY IN, but its use is
 deprecated and will eventually be removed.)  Binary COPY is supported.
-The CopyInResponse and CopyOutResponse messages carry a field indicating
+The CopyInResponse and CopyOutResponse messages include a field indicating
 whether the COPY operation is text or binary.
 </para>
 
@@ -3888,6 +3893,11 @@ Subsequently, a ParameterStatus message is sent whenever the active value
 changes for any of these parameters.
 </para>
 
+<para>
+The RowDescription ('<literal>T</>') message carries new table OID and column
+number fields for each column of the described row.
+</para>
+
 <para>
 The CursorResponse ('<literal>P</>') message is no longer generated by
 the backend.
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index c88dedd93fd62697ae96e68b8bee444986155136..160b703223f5d973f7d9eb6bfc6f93a44cac869b 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.66 2003/04/22 00:08:06 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.67 2003/04/26 20:22:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -98,6 +98,7 @@ printtup_setup(DestReceiver *self, int operation,
 	{
 		Form_pg_attribute *attrs = typeinfo->attrs;
 		int			natts = typeinfo->natts;
+		int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
 		int			i;
 		StringInfoData buf;
 
@@ -107,11 +108,19 @@ printtup_setup(DestReceiver *self, int operation,
 		for (i = 0; i < natts; ++i)
 		{
 			pq_sendstring(&buf, NameStr(attrs[i]->attname));
+			/* column ID info appears in protocol 3.0 and up */
+			if (proto >= 3)
+			{
+				/* XXX not yet implemented, send zeroes */
+				pq_sendint(&buf, 0, 4);
+				pq_sendint(&buf, 0, 2);
+			}
 			pq_sendint(&buf, (int) attrs[i]->atttypid,
 					   sizeof(attrs[i]->atttypid));
 			pq_sendint(&buf, attrs[i]->attlen,
 					   sizeof(attrs[i]->attlen));
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+			/* typmod appears in protocol 2.0 and up */
+			if (proto >= 2)
 				pq_sendint(&buf, attrs[i]->atttypmod,
 						   sizeof(attrs[i]->atttypmod));
 		}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 598e3c880e5ed0e0bb345d4a153dcf34ba8d52b1..a15985d3bd787f808142ef00283c5b39f4ccf511 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.145 2003/03/27 16:51:27 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.146 2003/04/26 20:22:59 tgl Exp $
  *
  * NOTES
  *		Transaction aborts can now occur two ways:
@@ -1705,17 +1705,44 @@ AbortOutOfAnyTransaction(void)
 	s->blockState = TBLOCK_DEFAULT;
 }
 
+/*
+ * IsTransactionBlock --- are we within a transaction block?
+ */
 bool
 IsTransactionBlock(void)
 {
 	TransactionState s = CurrentTransactionState;
 
-	if (s->blockState == TBLOCK_INPROGRESS
-		|| s->blockState == TBLOCK_ABORT
-		|| s->blockState == TBLOCK_ENDABORT)
-		return true;
+	if (s->blockState == TBLOCK_DEFAULT)
+		return false;
 
-	return false;
+	return true;
+}
+
+/*
+ * TransactionBlockStatusCode - return status code to send in ReadyForQuery
+ */
+char
+TransactionBlockStatusCode(void)
+{
+	TransactionState s = CurrentTransactionState;
+
+	switch (s->blockState)
+	{
+		case TBLOCK_DEFAULT:
+			return 'I';			/* idle --- not in transaction */
+		case TBLOCK_BEGIN:
+		case TBLOCK_INPROGRESS:
+		case TBLOCK_END:
+			return 'T';			/* in transaction */
+		case TBLOCK_ABORT:
+		case TBLOCK_ENDABORT:
+			return 'E';			/* in failed transaction */
+	}
+
+	/* should never get here */
+	elog(ERROR, "bogus transaction block state");
+	return 0;					/* keep compiler quiet */
 }
 
 
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 5ccaa60995c5ae39a4693c4a8ae877eab5ff13c1..41906a348a461fdd3b590f7a8a63187d7bba553f 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.53 2003/04/22 00:08:07 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.54 2003/04/26 20:22:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,6 +29,7 @@
 #include "postgres.h"
 
 #include "access/printtup.h"
+#include "access/xact.h"
 #include "executor/tstoreReceiver.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -177,6 +178,7 @@ NullCommand(CommandDest dest)
  *
  *		The ReadyForQuery message is sent in protocol versions 2.0 and up
  *		so that the FE can tell when we are done processing a query string.
+ *		In versions 3.0 and up, it also carries a transaction state indicator.
  *
  *		Note that by flushing the stdio buffer here, we can avoid doing it
  *		most other places and thus reduce the number of separate packets sent.
@@ -189,7 +191,15 @@ ReadyForQuery(CommandDest dest)
 	{
 		case RemoteInternal:
 		case Remote:
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+			{
+				StringInfoData buf;
+
+				pq_beginmessage(&buf, 'Z');
+				pq_sendbyte(&buf, TransactionBlockStatusCode());
+				pq_endmessage(&buf);
+			}
+			else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
 				pq_putemptymessage('Z');
 			/* Flush output at end of cycle in any case. */
 			pq_flush();
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index b3938c869e1f759a5df90b3ce6f7e16f37593935..5525ce74457eabd575a2ec6be2072f4a594e9343 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: xact.h,v 1.49 2003/01/10 22:03:30 petere Exp $
+ * $Id: xact.h,v 1.50 2003/04/26 20:22:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,9 +29,35 @@
 
 extern int	DefaultXactIsoLevel;
 extern int	XactIsoLevel;
+
+/* Xact read-only state */
 extern bool	DefaultXactReadOnly;
 extern bool	XactReadOnly;
 
+/*
+ *	transaction states - transaction state from server perspective
+ */
+typedef enum TransState
+{
+	TRANS_DEFAULT,
+	TRANS_START,
+	TRANS_INPROGRESS,
+	TRANS_COMMIT,
+	TRANS_ABORT
+} TransState;
+
+/*
+ *	transaction block states - transaction state of client queries
+ */
+typedef enum TBlockState
+{
+	TBLOCK_DEFAULT,
+	TBLOCK_BEGIN,
+	TBLOCK_INPROGRESS,
+	TBLOCK_END,
+	TBLOCK_ABORT,
+	TBLOCK_ENDABORT
+} TBlockState;
 
 /* ----------------
  *		transaction state structure
@@ -43,33 +69,17 @@ typedef struct TransactionStateData
 	CommandId	commandId;
 	AbsoluteTime startTime;
 	int			startTimeUsec;
-	int			state;
-	int			blockState;
+	TransState	state;
+	TBlockState	blockState;
 } TransactionStateData;
 
 typedef TransactionStateData *TransactionState;
 
-/*
- *	transaction states - transaction state from server perspective
- *	
- *	Syntax error could cause transaction to abort, but client code thinks
- *	it is still in a transaction, so we have to wait for COMMIT/ROLLBACK.
- */
-#define TRANS_DEFAULT			0
-#define TRANS_START				1
-#define TRANS_INPROGRESS		2
-#define TRANS_COMMIT			3
-#define TRANS_ABORT				4
 
-/*
- *	transaction block states - transaction state of client queries
+/* ----------------
+ *		transaction-related XLOG entries
+ * ----------------
  */
-#define TBLOCK_DEFAULT			0
-#define TBLOCK_BEGIN			1
-#define TBLOCK_INPROGRESS		2
-#define TBLOCK_END				3
-#define TBLOCK_ABORT			4
-#define TBLOCK_ENDABORT			5
 
 /*
  * XLOG allows to store some information in high 4 bits of log
@@ -115,6 +125,7 @@ extern void AbortCurrentTransaction(void);
 extern void BeginTransactionBlock(void);
 extern void EndTransactionBlock(void);
 extern bool IsTransactionBlock(void);
+extern char TransactionBlockStatusCode(void);
 extern void UserAbortTransactionBlock(void);
 extern void AbortOutOfAnyTransaction(void);
 extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 6a871c7c6f61ec427a0f327edc66b5f66586d8c1..d5374a68fe6d53edc0f5415194760165765d1160 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pqcomm.h,v 1.80 2003/04/25 19:45:09 tgl Exp $
+ * $Id: pqcomm.h,v 1.81 2003/04/26 20:22:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,7 +106,7 @@ typedef union SockAddr
 /* The earliest and latest frontend/backend protocol version supported. */
 
 #define PG_PROTOCOL_EARLIEST	PG_PROTOCOL(1,0)
-#define PG_PROTOCOL_LATEST		PG_PROTOCOL(3,104) /* XXX temporary value */
+#define PG_PROTOCOL_LATEST		PG_PROTOCOL(3,105) /* XXX temporary value */
 
 typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
 
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 53c4e2886225e7b6a1f3381ab9d77bec5c31fb0f..db513d64ef5df47c325c4be69d326a04fbc67b7c 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.133 2003/04/25 19:45:09 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.134 2003/04/26 20:22:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1027,6 +1027,8 @@ parseInput(PGconn *conn)
 					conn->asyncStatus = PGASYNC_READY;
 					break;
 				case 'Z':		/* backend is ready for new query */
+					if (pqGetc(&conn->xact_status, conn))
+						return;
 					conn->asyncStatus = PGASYNC_IDLE;
 					break;
 				case 'I':		/* empty query */
@@ -1222,11 +1224,15 @@ getRowDescriptions(PGconn *conn)
 	/* get type info */
 	for (i = 0; i < nfields; i++)
 	{
+		int			tableid;
+		int			columnid;
 		int			typid;
 		int			typlen;
 		int			atttypmod;
 
 		if (pqGets(&conn->workBuffer, conn) ||
+			pqGetInt(&tableid, 4, conn) ||
+			pqGetInt(&columnid, 2, conn) ||
 			pqGetInt(&typid, 4, conn) ||
 			pqGetInt(&typlen, 2, conn) ||
 			pqGetInt(&atttypmod, 4, conn))
@@ -1237,8 +1243,9 @@ getRowDescriptions(PGconn *conn)
 
 		/*
 		 * Since pqGetInt treats 2-byte integers as unsigned, we need to
-		 * coerce the result to signed form.
+		 * coerce these results to signed form.
 		 */
+		columnid = (int) ((int16) columnid);
 		typlen = (int) ((int16) typlen);
 
 		result->attDescs[i].name = pqResultStrdup(result,
@@ -1246,6 +1253,7 @@ getRowDescriptions(PGconn *conn)
 		result->attDescs[i].typid = typid;
 		result->attDescs[i].typlen = typlen;
 		result->attDescs[i].atttypmod = atttypmod;
+		/* XXX todo: save tableid/columnid too */
 	}
 
 	/* Success! */
@@ -2289,9 +2297,10 @@ PQfn(PGconn *conn,
 					continue;
 				break;
 			case 'Z':			/* backend is ready for new query */
+				if (pqGetc(&conn->xact_status, conn))
+					continue;
 				/* consume the message and exit */
 				conn->inStart += 5 + msgLength;
-				/* XXX expect additional fields here */
 				/* if we saved a result object (probably an error), use it */
 				if (conn->result)
 					return prepareAsyncResult(conn);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 12670f3a0a4e694e22e3ce24de7ac87f47ea865d..a5e6bceef4216315b922b0f443b1faa1f0082100 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-int.h,v 1.65 2003/04/25 19:45:10 tgl Exp $
+ * $Id: libpq-int.h,v 1.66 2003/04/26 20:23:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -56,7 +56,7 @@ typedef int ssize_t;			/* ssize_t doesn't exist in VC (atleast
  * pqcomm.h describe what the backend knows, not what libpq knows.
  */
 
-#define PG_PROTOCOL_LIBPQ	PG_PROTOCOL(3,104) /* XXX temporary value */
+#define PG_PROTOCOL_LIBPQ	PG_PROTOCOL(3,105) /* XXX temporary value */
 
 /*
  * POSTGRES backend dependent Constants.
@@ -241,6 +241,7 @@ struct pg_conn
 	/* Status indicators */
 	ConnStatusType status;
 	PGAsyncStatusType asyncStatus;
+	char		xact_status;	/* status flag from latest ReadyForQuery */
 	char		copy_is_binary;	/* 1 = copy binary, 0 = copy text */
 	int			copy_already_done; /* # bytes already returned in COPY OUT */
 	int			nonblocking;	/* whether this connection is using a