diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8903d6b42dd61f896408a52f0b30d4bd628cfc19..77ccd64a7da9303ceed67cf27f0dec048df9af77 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.345 2006/08/02 01:59:45 joe Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.346 2006/08/10 02:36:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1793,6 +1793,7 @@ _copySetOperationStmt(SetOperationStmt *from)
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 	COPY_NODE_FIELD(colTypes);
+	COPY_NODE_FIELD(colTypmods);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index d49e02b3d8afff85df7a6aa050ce9dff2292a986..4b749e0fc6d27742cdb187233d830ddab620d687 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.279 2006/08/02 01:59:45 joe Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.280 2006/08/10 02:36:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -743,6 +743,7 @@ _equalSetOperationStmt(SetOperationStmt *a, SetOperationStmt *b)
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 	COMPARE_NODE_FIELD(colTypes);
+	COMPARE_NODE_FIELD(colTypmods);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 34555e1c56781c947f6c6a0f85ca33c301ba2ff7..39ac8e4c621815b859ffd45b8e958456a870b927 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.280 2006/08/02 01:59:45 joe Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.281 2006/08/10 02:36:28 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -1574,6 +1574,7 @@ _outSetOperationStmt(StringInfo str, SetOperationStmt *node)
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 	WRITE_NODE_FIELD(colTypes);
+	WRITE_NODE_FIELD(colTypmods);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 7459acb30c006804cf3a8497ad391602ab642944..80fa88e0da5ecbd5c6076cd1daea43f92a085b09 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.193 2006/08/02 01:59:45 joe Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.194 2006/08/10 02:36:28 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -245,6 +245,7 @@ _readSetOperationStmt(void)
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
 	READ_NODE_FIELD(colTypes);
+	READ_NODE_FIELD(colTypmods);
 
 	READ_DONE();
 }
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 1d7e20c1e8b957ed329103dd026e50839a188176..fc618f72d1cc0878fafc8bff66a113c718de9391 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.150 2006/08/02 01:59:45 joe Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.151 2006/08/10 02:36:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -813,6 +813,10 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
  * Compare tlist's datatypes against the list of set-operation result types.
  * For any items that are different, mark the appropriate element of
  * differentTypes[] to show that this column will have type conversions.
+ *
+ * We don't have to care about typmods here: the only allowed difference
+ * between set-op input and output typmods is input is a specific typmod
+ * and output is -1, and that does not require a coercion.
  */
 static void
 compare_tlist_datatypes(List *tlist, List *colTypes,
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 856181d090dfbcda4da447972ae0da7750669c70..2fe78473048451951a57293458d7c37028dbb257 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.39 2006/07/14 14:52:21 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.40 2006/08/10 02:36:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -716,6 +716,7 @@ is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, List *colTypes)
 		Assert(subquery != NULL);
 
 		/* Leaf nodes are OK if they match the toplevel column types */
+		/* We don't have to compare typmods here */
 		return tlist_same_datatypes(subquery->targetList, colTypes, true);
 	}
 	else if (IsA(setOp, SetOperationStmt))
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 3b9e740cadff34fb4d405dcf9953480ebbe795fe..3bf7223199f06a66968cb6cdc52d676fdf3c5581 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -22,7 +22,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.132 2006/04/30 18:30:39 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.133 2006/08/10 02:36:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -152,6 +152,10 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction,
  * flag: if >= 0, add a resjunk output column indicating value of flag
  * refnames_tlist: targetlist to take column names from
  * *sortClauses: receives list of SortClauses for result plan, if any
+ *
+ * We don't have to care about typmods here: the only allowed difference
+ * between set-op input and output typmods is input is a specific typmod
+ * and output is -1, and that does not require a coercion.
  */
 static Plan *
 recurse_set_operations(Node *setOp, PlannerInfo *root,
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index c75c496065c2af4af120c6839cb7c4c6b0144814..74ebb3fc24b3723db9c2663d2b83d012a0bc8ea0 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.72 2006/03/05 15:58:32 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.73 2006/08/10 02:36:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -174,6 +174,8 @@ get_sortgrouplist_exprs(List *sortClauses, List *targetList)
  *
  * Resjunk columns are ignored if junkOK is true; otherwise presence of
  * a resjunk column will always cause a 'false' result.
+ *
+ * Note: currently no callers care about comparing typmods.
  */
 bool
 tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK)
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 4f00d98b9b696edb430bf24e7fca75429c6e0842..6921e1d77c83842115968180da0a56aecef954b5 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.343 2006/08/02 14:14:22 tgl Exp $
+ *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.344 2006/08/10 02:36:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -131,7 +131,8 @@ static void transformFKConstraints(ParseState *pstate,
 					   bool skipValidation,
 					   bool isAddConstraint);
 static void applyColumnNames(List *dst, List *src);
-static List *getSetColTypes(ParseState *pstate, Node *node);
+static void getSetColTypes(ParseState *pstate, Node *node,
+						   List **colTypes, List **colTypmods);
 static void transformLockingClause(Query *qry, LockingClause *lc);
 static void transformConstraintAttrs(List *constraintList);
 static void transformColumnType(ParseState *pstate, ColumnDef *column);
@@ -2312,7 +2313,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	List	   *lockingClause;
 	Node	   *node;
 	ListCell   *left_tlist,
-			   *dtlist,
+			   *lct,
+			   *lcm,
 			   *l;
 	List	   *targetvars,
 			   *targetnames,
@@ -2395,9 +2397,10 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	targetnames = NIL;
 	left_tlist = list_head(leftmostQuery->targetList);
 
-	foreach(dtlist, sostmt->colTypes)
+	forboth(lct, sostmt->colTypes, lcm, sostmt->colTypmods)
 	{
-		Oid			colType = lfirst_oid(dtlist);
+		Oid			colType = lfirst_oid(lct);
+		int32		colTypmod = lfirst_int(lcm);
 		TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
 		char	   *colName;
 		TargetEntry *tle;
@@ -2408,7 +2411,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 		expr = (Expr *) makeVar(leftmostRTI,
 								lefttle->resno,
 								colType,
-								-1,
+								colTypmod,
 								0);
 		tle = makeTargetEntry(expr,
 							  (AttrNumber) pstate->p_next_resno++,
@@ -2609,8 +2612,12 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *lcoltypes;
 		List	   *rcoltypes;
-		ListCell   *l;
-		ListCell   *r;
+		List	   *lcoltypmods;
+		List	   *rcoltypmods;
+		ListCell   *lct;
+		ListCell   *rct;
+		ListCell   *lcm;
+		ListCell   *rcm;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -2630,24 +2637,43 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 		 * Verify that the two children have the same number of non-junk
 		 * columns, and determine the types of the merged output columns.
 		 */
-		lcoltypes = getSetColTypes(pstate, op->larg);
-		rcoltypes = getSetColTypes(pstate, op->rarg);
+		getSetColTypes(pstate, op->larg, &lcoltypes, &lcoltypmods);
+		getSetColTypes(pstate, op->rarg, &rcoltypes, &rcoltypmods);
 		if (list_length(lcoltypes) != list_length(rcoltypes))
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("each %s query must have the same number of columns",
 						context)));
+		Assert(list_length(lcoltypes) == list_length(lcoltypmods));
+		Assert(list_length(rcoltypes) == list_length(rcoltypmods));
 
 		op->colTypes = NIL;
-		forboth(l, lcoltypes, r, rcoltypes)
+		op->colTypmods = NIL;
+		/* don't have a "foreach4", so chase two of the lists by hand */
+		lcm = list_head(lcoltypmods);
+		rcm = list_head(rcoltypmods);
+		forboth(lct, lcoltypes, rct, rcoltypes)
 		{
-			Oid			lcoltype = lfirst_oid(l);
-			Oid			rcoltype = lfirst_oid(r);
+			Oid			lcoltype = lfirst_oid(lct);
+			Oid			rcoltype = lfirst_oid(rct);
+			int32		lcoltypmod = lfirst_int(lcm);
+			int32		rcoltypmod = lfirst_int(rcm);
 			Oid			rescoltype;
+			int32		rescoltypmod;
 
+			/* select common type, same as CASE et al */
 			rescoltype = select_common_type(list_make2_oid(lcoltype, rcoltype),
 											context);
+			/* if same type and same typmod, use typmod; else default */
+			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+				rescoltypmod = lcoltypmod;
+			else
+				rescoltypmod = -1;
 			op->colTypes = lappend_oid(op->colTypes, rescoltype);
+			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+
+			lcm = lnext(lcm);
+			rcm = lnext(rcm);
 		}
 
 		return (Node *) op;
@@ -2656,17 +2682,19 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 
 /*
  * getSetColTypes
- *		Get output column types of an (already transformed) set-op node
+ *	  Get output column types/typmods of an (already transformed) set-op node
  */
-static List *
-getSetColTypes(ParseState *pstate, Node *node)
+static void
+getSetColTypes(ParseState *pstate, Node *node,
+			   List **colTypes, List **colTypmods)
 {
+	*colTypes = NIL;
+	*colTypmods = NIL;
 	if (IsA(node, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) node;
 		RangeTblEntry *rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
 		Query	   *selectQuery = rte->subquery;
-		List	   *result = NIL;
 		ListCell   *tl;
 
 		Assert(selectQuery != NULL);
@@ -2677,9 +2705,11 @@ getSetColTypes(ParseState *pstate, Node *node)
 
 			if (tle->resjunk)
 				continue;
-			result = lappend_oid(result, exprType((Node *) tle->expr));
+			*colTypes = lappend_oid(*colTypes,
+									exprType((Node *) tle->expr));
+			*colTypmods = lappend_int(*colTypmods,
+									  exprTypmod((Node *) tle->expr));
 		}
-		return result;
 	}
 	else if (IsA(node, SetOperationStmt))
 	{
@@ -2687,13 +2717,11 @@ getSetColTypes(ParseState *pstate, Node *node)
 
 		/* Result already computed during transformation of node */
 		Assert(op->colTypes != NIL);
-		return op->colTypes;
+		*colTypes = op->colTypes;
+		*colTypmods = op->colTypmods;
 	}
 	else
-	{
 		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
-		return NIL;				/* keep compiler quiet */
-	}
 }
 
 /* Attach column names from a ColumnDef list to a TargetEntry list */
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index dbf03959054064e978af6b0863e16bba42310278..b97c3d07ec18621db56cd7f623bcbdd876e101e7 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.347 2006/08/06 03:53:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.348 2006/08/10 02:36:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200608051
+#define CATALOG_VERSION_NO	200608091
 
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d0fa16ff51c63dc104c5cef42caac3a2d5d58c48..e2567ff8e441e75e4271fc26513f0cb6da52bf18 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.320 2006/08/02 01:59:47 joe Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.321 2006/08/10 02:36:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -769,7 +769,8 @@ typedef struct SetOperationStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 
 	/* Fields derived during parse analysis: */
-	List	   *colTypes;		/* list of OIDs of output column types */
+	List	   *colTypes;		/* OID list of output column type OIDs */
+	List	   *colTypmods;		/* integer list of output column typmods */
 } SetOperationStmt;