diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index bce0e076837f57857c8c19d82b08f5cb51b1f766..9e2028a64ff6d2d5c67e5f493ba8271966d70d48 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1357,6 +1357,9 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
  * on the datatype, and OpExpr nodes depend on the operator which depends on
  * the datatype.  However we do need a type dependency if there is no such
  * indirect dependency, as for example in Const and CoerceToDomain nodes.
+ *
+ * Similarly, we don't need to create dependencies on collations except where
+ * the collation is being freshly introduced to the expression.
  */
 static bool
 find_expr_references_walker(Node *node,
@@ -1425,7 +1428,15 @@ find_expr_references_walker(Node *node,
 		/* A constant must depend on the constant's datatype */
 		add_object_address(OCLASS_TYPE, con->consttype, 0,
 						   context->addrs);
-		if (OidIsValid(con->constcollid))
+
+		/*
+		 * We must also depend on the constant's collation: it could be
+		 * different from the datatype's, if a CollateExpr was const-folded
+		 * to a simple constant.  However we can save work in the most common
+		 * case where the collation is "default", since we know that's pinned.
+		 */
+		if (OidIsValid(con->constcollid) &&
+			con->constcollid != DEFAULT_COLLATION_OID)
 			add_object_address(OCLASS_COLLATION, con->constcollid, 0,
 							   context->addrs);
 
@@ -1494,7 +1505,9 @@ find_expr_references_walker(Node *node,
 		/* A parameter must depend on the parameter's datatype */
 		add_object_address(OCLASS_TYPE, param->paramtype, 0,
 						   context->addrs);
-		if (OidIsValid(param->paramcollation))
+		/* and its collation, just as for Consts */
+		if (OidIsValid(param->paramcollation) &&
+			param->paramcollation != DEFAULT_COLLATION_OID)
 			add_object_address(OCLASS_COLLATION, param->paramcollation, 0,
 							   context->addrs);
 	}
@@ -1567,13 +1580,6 @@ find_expr_references_walker(Node *node,
 		add_object_address(OCLASS_TYPE, relab->resulttype, 0,
 						   context->addrs);
 	}
-	else if (IsA(node, CollateClause))
-	{
-		CollateClause *coll = (CollateClause *) node;
-
-		add_object_address(OCLASS_COLLATION, coll->collOid, 0,
-						   context->addrs);
-	}
 	else if (IsA(node, CoerceViaIO))
 	{
 		CoerceViaIO *iocoerce = (CoerceViaIO *) node;
@@ -1601,6 +1607,13 @@ find_expr_references_walker(Node *node,
 		add_object_address(OCLASS_TYPE, cvt->resulttype, 0,
 						   context->addrs);
 	}
+	else if (IsA(node, CollateExpr))
+	{
+		CollateExpr *coll = (CollateExpr *) node;
+
+		add_object_address(OCLASS_COLLATION, coll->collOid, 0,
+						   context->addrs);
+	}
 	else if (IsA(node, RowExpr))
 	{
 		RowExpr    *rowexpr = (RowExpr *) node;
@@ -1652,10 +1665,11 @@ find_expr_references_walker(Node *node,
 
 		/*
 		 * Add whole-relation refs for each plain relation mentioned in the
-		 * subquery's rtable, as well as datatype refs for any datatypes used
-		 * as a RECORD function's output.  (Note: query_tree_walker takes care
-		 * of recursing into RTE_FUNCTION RTEs, subqueries, etc, so no need to
-		 * do that here.  But keep it from looking at join alias lists.)
+		 * subquery's rtable, as well as refs for any datatypes and collations
+		 * used in a RECORD function's output.  (Note: query_tree_walker takes
+		 * care of recursing into RTE_FUNCTION RTEs, subqueries, etc, so no
+		 * need to do that here.  But keep it from looking at join alias
+		 * lists.)
 		 */
 		foreach(lc, query->rtable)
 		{
@@ -1678,7 +1692,8 @@ find_expr_references_walker(Node *node,
 					{
 						Oid collid = lfirst_oid(ct);
 
-						if (OidIsValid(collid))
+						if (OidIsValid(collid) &&
+							collid != DEFAULT_COLLATION_OID)
 							add_object_address(OCLASS_COLLATION, collid, 0,
 											   context->addrs);
 					}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 3513256b9a54874b9c42f1489aff67c1bbcb01ae..ee3bca17d17a3419b2eb8fdf22fed98e22cfce02 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -831,7 +831,7 @@ DefineDomain(CreateDomainStmt *stmt)
 	 */
 	baseColl = baseType->typcollation;
 	if (stmt->collClause)
-		domaincoll = get_collation_oid(stmt->collClause->collnames, false);
+		domaincoll = get_collation_oid(stmt->collClause->collname, false);
 	else
 		domaincoll = baseColl;
 
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 2b5dd2dbf85e46efd7f48db684256656d91983f4..0faf52dfd793b1167624f3951bdc6d33330720ed 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -120,6 +120,9 @@ static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext,
 static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 					   ExprContext *econtext,
 					   bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalCollateExpr(GenericExprState *exprstate,
+					ExprContext *econtext,
+					bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 			 bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
@@ -166,9 +169,6 @@ static Datum ExecEvalFieldStore(FieldStoreState *fstate,
 static Datum ExecEvalRelabelType(GenericExprState *exprstate,
 					ExprContext *econtext,
 					bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalCollateClause(GenericExprState *exprstate,
-					ExprContext *econtext,
-					bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
 					ExprContext *econtext,
 					bool *isNull, ExprDoneCond *isDone);
@@ -2753,6 +2753,20 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 	return HeapTupleGetDatum(result);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalCollateExpr
+ *
+ *		Evaluate a CollateExpr node.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalCollateExpr(GenericExprState *exprstate,
+					ExprContext *econtext,
+					bool *isNull, ExprDoneCond *isDone)
+{
+	return ExecEvalExpr(exprstate->arg, econtext, isNull, isDone);
+}
+
 /* ----------------------------------------------------------------
  *		ExecEvalCase
  *
@@ -4028,20 +4042,6 @@ ExecEvalRelabelType(GenericExprState *exprstate,
 	return ExecEvalExpr(exprstate->arg, econtext, isNull, isDone);
 }
 
-/* ----------------------------------------------------------------
- *		ExecEvalCollateClause
- *
- *		Evaluate a CollateClause node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCollateClause(GenericExprState *exprstate,
-					ExprContext *econtext,
-					bool *isNull, ExprDoneCond *isDone)
-{
-	return ExecEvalExpr(exprstate->arg, econtext, isNull, isDone);
-}
-
 /* ----------------------------------------------------------------
  *		ExecEvalCoerceViaIO
  *
@@ -4501,16 +4501,6 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				state = (ExprState *) gstate;
 			}
 			break;
-		case T_CollateClause:
-			{
-				CollateClause *collate = (CollateClause *) node;
-				GenericExprState *gstate = makeNode(GenericExprState);
-
-				gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCollateClause;
-				gstate->arg = ExecInitExpr(collate->arg, parent);
-				state = (ExprState *) gstate;
-			}
-			break;
 		case T_CoerceViaIO:
 			{
 				CoerceViaIO *iocoerce = (CoerceViaIO *) node;
@@ -4561,6 +4551,16 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				state = (ExprState *) cstate;
 			}
 			break;
+		case T_CollateExpr:
+			{
+				CollateExpr *collate = (CollateExpr *) node;
+				GenericExprState *gstate = makeNode(GenericExprState);
+
+				gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCollateExpr;
+				gstate->arg = ExecInitExpr(collate->arg, parent);
+				state = (ExprState *) gstate;
+			}
+			break;
 		case T_CaseExpr:
 			{
 				CaseExpr   *caseexpr = (CaseExpr *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b948af604d8149b9d95f9c923cbb983b2df3eb40..c0490e93ea50c045aff08da3ed6a16bcca5b545b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1434,6 +1434,21 @@ _copyConvertRowtypeExpr(ConvertRowtypeExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyCollateExpr
+ */
+static CollateExpr *
+_copyCollateExpr(CollateExpr *from)
+{
+	CollateExpr   *newnode = makeNode(CollateExpr);
+
+	COPY_NODE_FIELD(arg);
+	COPY_SCALAR_FIELD(collOid);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /*
  * _copyCaseExpr
  */
@@ -2260,8 +2275,7 @@ _copyCollateClause(CollateClause *from)
 	CollateClause   *newnode = makeNode(CollateClause);
 
 	COPY_NODE_FIELD(arg);
-	COPY_NODE_FIELD(collnames);
-	COPY_SCALAR_FIELD(collOid);
+	COPY_NODE_FIELD(collname);
 	COPY_LOCATION_FIELD(location);
 
 	return newnode;
@@ -4017,6 +4031,9 @@ copyObject(void *from)
 		case T_ConvertRowtypeExpr:
 			retval = _copyConvertRowtypeExpr(from);
 			break;
+		case T_CollateExpr:
+			retval = _copyCollateExpr(from);
+			break;
 		case T_CaseExpr:
 			retval = _copyCaseExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c8ee4744364e7b925415c66c23d0bc2d78b68ffa..3726006f1d2fd5e0eaf77521777a749dd39bf363 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -493,6 +493,16 @@ _equalConvertRowtypeExpr(ConvertRowtypeExpr *a, ConvertRowtypeExpr *b)
 	return true;
 }
 
+static bool
+_equalCollateExpr(CollateExpr *a, CollateExpr *b)
+{
+	COMPARE_NODE_FIELD(arg);
+	COMPARE_SCALAR_FIELD(collOid);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 static bool
 _equalCaseExpr(CaseExpr *a, CaseExpr *b)
 {
@@ -2149,8 +2159,7 @@ static bool
 _equalCollateClause(CollateClause *a, CollateClause *b)
 {
 	COMPARE_NODE_FIELD(arg);
-	COMPARE_NODE_FIELD(collnames);
-	COMPARE_SCALAR_FIELD(collOid);
+	COMPARE_NODE_FIELD(collname);
 	COMPARE_LOCATION_FIELD(location);
 
 	return true;
@@ -2583,6 +2592,9 @@ equal(void *a, void *b)
 		case T_ConvertRowtypeExpr:
 			retval = _equalConvertRowtypeExpr(a, b);
 			break;
+		case T_CollateExpr:
+			retval = _equalCollateExpr(a, b);
+			break;
 		case T_CaseExpr:
 			retval = _equalCaseExpr(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c3c5d8e6e5c3e910e6caf953d228e5bf13d60a1b..5394851a1f55bfcb744b0680da4883f16a8bdef1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -162,9 +162,6 @@ exprType(Node *expr)
 		case T_RelabelType:
 			type = ((RelabelType *) expr)->resulttype;
 			break;
-		case T_CollateClause:
-			type = exprType((Node *) ((CollateClause *) expr)->arg);
-			break;
 		case T_CoerceViaIO:
 			type = ((CoerceViaIO *) expr)->resulttype;
 			break;
@@ -174,6 +171,9 @@ exprType(Node *expr)
 		case T_ConvertRowtypeExpr:
 			type = ((ConvertRowtypeExpr *) expr)->resulttype;
 			break;
+		case T_CollateExpr:
+			type = exprType((Node *) ((CollateExpr *) expr)->arg);
+			break;
 		case T_CaseExpr:
 			type = ((CaseExpr *) expr)->casetype;
 			break;
@@ -321,6 +321,8 @@ exprTypmod(Node *expr)
 			return ((RelabelType *) expr)->resulttypmod;
 		case T_ArrayCoerceExpr:
 			return ((ArrayCoerceExpr *) expr)->resulttypmod;
+		case T_CollateExpr:
+			return exprTypmod((Node *) ((CollateExpr *) expr)->arg);
 		case T_CaseExpr:
 			{
 				/*
@@ -571,9 +573,6 @@ exprCollation(Node *expr)
 		case T_RelabelType:
 			coll = exprCollation((Node *) ((RelabelType *) expr)->arg);
 			break;
-		case T_CollateClause:
-			coll = ((CollateClause *) expr)->collOid;
-			break;
 		case T_CoerceViaIO:
 		{
 			CoerceViaIO *cvio = (CoerceViaIO *) expr;
@@ -592,6 +591,9 @@ exprCollation(Node *expr)
 			coll = coercion_expression_result_collation(cre->resulttype, (Node *) cre->arg);
 			break;
 		}
+		case T_CollateExpr:
+			coll = ((CollateExpr *) expr)->collOid;
+			break;
 		case T_CaseExpr:
 			coll = ((CaseExpr *) expr)->casecollation;
 			break;
@@ -989,6 +991,10 @@ exprLocation(Node *expr)
 								  exprLocation((Node *) cexpr->arg));
 			}
 			break;
+		case T_CollateExpr:
+			/* just use argument's location */
+			loc = exprLocation((Node *) ((CollateExpr *) expr)->arg);
+			break;
 		case T_CaseExpr:
 			/* CASE keyword should always be the first thing */
 			loc = ((CaseExpr *) expr)->location;
@@ -1122,7 +1128,8 @@ exprLocation(Node *expr)
 			}
 			break;
 		case T_CollateClause:
-			loc = ((CollateClause *) expr)->location;
+			/* just use argument's location */
+			loc = exprLocation(((CollateClause *) expr)->arg);
 			break;
 		case T_SortBy:
 			/* just use argument's location (ignore operator, if any) */
@@ -1436,14 +1443,14 @@ expression_tree_walker(Node *node,
 			break;
 		case T_RelabelType:
 			return walker(((RelabelType *) node)->arg, context);
-		case T_CollateClause:
-			return walker(((CollateClause *) node)->arg, context);
 		case T_CoerceViaIO:
 			return walker(((CoerceViaIO *) node)->arg, context);
 		case T_ArrayCoerceExpr:
 			return walker(((ArrayCoerceExpr *) node)->arg, context);
 		case T_ConvertRowtypeExpr:
 			return walker(((ConvertRowtypeExpr *) node)->arg, context);
+		case T_CollateExpr:
+			return walker(((CollateExpr *) node)->arg, context);
 		case T_CaseExpr:
 			{
 				CaseExpr   *caseexpr = (CaseExpr *) node;
@@ -1993,16 +2000,6 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
-		case T_CollateClause:
-			{
-				CollateClause *collate = (CollateClause *) node;
-				CollateClause *newnode;
-
-				FLATCOPY(newnode, collate, CollateClause);
-				MUTATE(newnode->arg, collate->arg, Expr *);
-				return (Node *) newnode;
-			}
-			break;
 		case T_CoerceViaIO:
 			{
 				CoerceViaIO *iocoerce = (CoerceViaIO *) node;
@@ -2033,6 +2030,16 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_CollateExpr:
+			{
+				CollateExpr *collate = (CollateExpr *) node;
+				CollateExpr *newnode;
+
+				FLATCOPY(newnode, collate, CollateExpr);
+				MUTATE(newnode->arg, collate->arg, Expr *);
+				return (Node *) newnode;
+			}
+			break;
 		case T_CaseExpr:
 			{
 				CaseExpr   *caseexpr = (CaseExpr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 06fd7ff818e4ed45c7cbb42ab2ef1f38848839bb..d56e4dac011cd613eaa152df59282cf72ac1890d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1195,6 +1195,16 @@ _outConvertRowtypeExpr(StringInfo str, ConvertRowtypeExpr *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outCollateExpr(StringInfo str, CollateExpr *node)
+{
+	WRITE_NODE_TYPE("COLLATE");
+
+	WRITE_NODE_FIELD(arg);
+	WRITE_OID_FIELD(collOid);
+	WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outCaseExpr(StringInfo str, CaseExpr *node)
 {
@@ -2104,11 +2114,10 @@ _outTypeCast(StringInfo str, TypeCast *node)
 static void
 _outCollateClause(StringInfo str, CollateClause *node)
 {
-	WRITE_NODE_TYPE("COLLATE");
+	WRITE_NODE_TYPE("COLLATECLAUSE");
 
 	WRITE_NODE_FIELD(arg);
-	WRITE_NODE_FIELD(collnames);
-	WRITE_OID_FIELD(collOid);
+	WRITE_NODE_FIELD(collname);
 	WRITE_LOCATION_FIELD(location);
 }
 
@@ -2829,9 +2838,6 @@ _outNode(StringInfo str, void *obj)
 			case T_RelabelType:
 				_outRelabelType(str, obj);
 				break;
-			case T_CollateClause:
-				_outCollateClause(str, obj);
-				break;
 			case T_CoerceViaIO:
 				_outCoerceViaIO(str, obj);
 				break;
@@ -2841,6 +2847,9 @@ _outNode(StringInfo str, void *obj)
 			case T_ConvertRowtypeExpr:
 				_outConvertRowtypeExpr(str, obj);
 				break;
+			case T_CollateExpr:
+				_outCollateExpr(str, obj);
+				break;
 			case T_CaseExpr:
 				_outCaseExpr(str, obj);
 				break;
@@ -3020,6 +3029,9 @@ _outNode(StringInfo str, void *obj)
 			case T_TypeCast:
 				_outTypeCast(str, obj);
 				break;
+			case T_CollateClause:
+				_outCollateClause(str, obj);
+				break;
 			case T_IndexElem:
 				_outIndexElem(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 09c5e25012c21e73fa103afcd2d17f0482e35dbe..6da61285b004570cd91ebf36c1799ded9bfcacff 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -743,22 +743,6 @@ _readRelabelType(void)
 	READ_DONE();
 }
 
-/*
- * _readCollateClause
- */
-static CollateClause *
-_readCollateClause(void)
-{
-	READ_LOCALS(CollateClause);
-
-	READ_NODE_FIELD(arg);
-	READ_NODE_FIELD(collnames);
-	READ_OID_FIELD(collOid);
-	READ_LOCATION_FIELD(location);
-
-	READ_DONE();
-}
-
 /*
  * _readCoerceViaIO
  */
@@ -810,6 +794,21 @@ _readConvertRowtypeExpr(void)
 	READ_DONE();
 }
 
+/*
+ * _readCollateExpr
+ */
+static CollateExpr *
+_readCollateExpr(void)
+{
+	READ_LOCALS(CollateExpr);
+
+	READ_NODE_FIELD(arg);
+	READ_OID_FIELD(collOid);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
 /*
  * _readCaseExpr
  */
@@ -1286,14 +1285,14 @@ parseNodeString(void)
 		return_value = _readFieldStore();
 	else if (MATCH("RELABELTYPE", 11))
 		return_value = _readRelabelType();
-	else if (MATCH("COLLATE", 7))
-		return_value = _readCollateClause();
 	else if (MATCH("COERCEVIAIO", 11))
 		return_value = _readCoerceViaIO();
 	else if (MATCH("ARRAYCOERCEEXPR", 15))
 		return_value = _readArrayCoerceExpr();
 	else if (MATCH("CONVERTROWTYPEEXPR", 18))
 		return_value = _readConvertRowtypeExpr();
+	else if (MATCH("COLLATE", 7))
+		return_value = _readCollateExpr();
 	else if (MATCH("CASE", 4))
 		return_value = _readCaseExpr();
 	else if (MATCH("WHEN", 4))
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index fa0952618b13367029be0ea21712c972d58237b8..8503792df448ccde6b03d728e61d28f6fb81c6a6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1308,6 +1308,12 @@ find_nonnullable_rels_walker(Node *node, bool top_level)
 
 		result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);
 	}
+	else if (IsA(node, CollateExpr))
+	{
+		CollateExpr *expr = (CollateExpr *) node;
+
+		result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);
+	}
 	else if (IsA(node, NullTest))
 	{
 		/* IS NOT NULL can be considered strict, but only at top level */
@@ -1510,6 +1516,12 @@ find_nonnullable_vars_walker(Node *node, bool top_level)
 
 		result = find_nonnullable_vars_walker((Node *) expr->arg, top_level);
 	}
+	else if (IsA(node, CollateExpr))
+	{
+		CollateExpr *expr = (CollateExpr *) node;
+
+		result = find_nonnullable_vars_walker((Node *) expr->arg, top_level);
+	}
 	else if (IsA(node, NullTest))
 	{
 		/* IS NOT NULL can be considered strict, but only at top level */
@@ -2580,6 +2592,42 @@ eval_const_expressions_mutator(Node *node,
 		/* Else we must return the partially-simplified node */
 		return (Node *) newexpr;
 	}
+	if (IsA(node, CollateExpr))
+	{
+		/*
+		 * If we can simplify the input to a constant, then we don't need the
+		 * CollateExpr node anymore: just change the constcollid field of the
+		 * Const node.  Otherwise, must copy the CollateExpr node.
+		 */
+		CollateExpr *collate = (CollateExpr *) node;
+		Node	   *arg;
+
+		arg = eval_const_expressions_mutator((Node *) collate->arg,
+											 context);
+
+		/*
+		 * If we find stacked CollateExprs, we can discard all but the top one.
+		 */
+		while (arg && IsA(arg, CollateExpr))
+			arg = (Node *) ((CollateExpr *) arg)->arg;
+
+		if (arg && IsA(arg, Const))
+		{
+			Const	   *con = (Const *) arg;
+
+			con->constcollid = collate->collOid;
+			return (Node *) con;
+		}
+		else
+		{
+			CollateExpr *newcollate = makeNode(CollateExpr);
+
+			newcollate->arg = (Expr *) arg;
+			newcollate->collOid = collate->collOid;
+			newcollate->location = collate->location;
+			return (Node *) newcollate;
+		}
+	}
 	if (IsA(node, CaseExpr))
 	{
 		/*----------
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 373d2adc71c90a58318e9c46e0c55024ab7caede..1633499f93974b2e0c1cf634f8ef2738cd292c02 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1993,8 +1993,7 @@ opt_collate_clause:
 				{
 					CollateClause *n = makeNode(CollateClause);
 					n->arg = NULL;
-					n->collnames = $2;
-					n->collOid = InvalidOid;
+					n->collname = $2;
 					n->location = @1;
 					$$ = (Node *) n;
 				}
@@ -2537,8 +2536,7 @@ ColConstraint:
 					 */
 					CollateClause *n = makeNode(CollateClause);
 					n->arg = NULL;
-					n->collnames = $2;
-					n->collOid = InvalidOid;
+					n->collname = $2;
 					n->location = @1;
 					$$ = (Node *) n;
 				}
@@ -9690,8 +9688,8 @@ a_expr:		c_expr									{ $$ = $1; }
 			| a_expr COLLATE any_name
 				{
 					CollateClause *n = makeNode(CollateClause);
-					n->arg = (Expr *) $1;
-					n->collnames = $3;
+					n->arg = $1;
+					n->collname = $3;
 					n->location = @2;
 					$$ = (Node *) n;
 				}
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 2fd808d26b200acaace440b464b5cfee4c975c1b..6aff34dd90d5b2e6124787e242bcc76a476aa318 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -279,11 +279,17 @@ coerce_type(ParseState *pstate, Node *node,
 		if (result)
 			return result;
 	}
-	if (IsA(node, CollateClause))
+	if (IsA(node, CollateExpr))
 	{
-		CollateClause *cc = (CollateClause *) node;
+		/*
+		 * XXX very ugly kluge to push the coercion underneath the CollateExpr.
+		 * This needs to be rethought, as it almost certainly doesn't cover
+		 * all cases.
+		 */
+		CollateExpr *cc = (CollateExpr *) node;
 
-		cc->arg = (Expr *) coerce_type(pstate, (Node *) cc->arg, inputTypeId, targetTypeId, targetTypeMod,
+		cc->arg = (Expr *) coerce_type(pstate, (Node *) cc->arg,
+									   inputTypeId, targetTypeId, targetTypeMod,
 									   ccontext, cformat, location);
 		return (Node *) cc;
 	}
@@ -2121,7 +2127,7 @@ select_common_collation(ParseState *pstate, List *exprs, bool none_ok)
 	{
 		Node	   *pexpr = (Node *) lfirst(lc);
 		Oid			pcoll = exprCollation(pexpr);
-		bool		pexplicit = IsA(pexpr, CollateClause);
+		bool		pexplicit = IsA(pexpr, CollateExpr);
 
 		if (pcoll && pexplicit)
 		{
@@ -2130,7 +2136,7 @@ select_common_collation(ParseState *pstate, List *exprs, bool none_ok)
 			{
 				Node	   *nexpr = (Node *) lfirst(lc2);
 				Oid			ncoll = exprCollation(nexpr);
-				bool		nexplicit = IsA(nexpr, CollateClause);
+				bool		nexplicit = IsA(nexpr, CollateExpr);
 
 				if (!ncoll || !nexplicit)
 					continue;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7a4f8cc2497aaf9f669dc3b1d65016a383f425da..17bd2bf50aee9d5322659b94621d4762b6348120 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -318,6 +318,7 @@ transformExpr(ParseState *pstate, Node *expr)
 		case T_CoerceViaIO:
 		case T_ArrayCoerceExpr:
 		case T_ConvertRowtypeExpr:
+		case T_CollateExpr:
 		case T_CaseTestExpr:
 		case T_ArrayExpr:
 		case T_CoerceToDomain:
@@ -2103,11 +2104,11 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 static Node *
 transformCollateClause(ParseState *pstate, CollateClause *c)
 {
-	CollateClause *newc;
+	CollateExpr *newc;
 	Oid		argtype;
 
-	newc = makeNode(CollateClause);
-	newc->arg = (Expr *) transformExpr(pstate, (Node *) c->arg);
+	newc = makeNode(CollateExpr);
+	newc->arg = (Expr *) transformExpr(pstate, c->arg);
 
 	argtype = exprType((Node *) newc->arg);
 	/*
@@ -2121,8 +2122,7 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 						format_type_be(argtype)),
 				 parser_errposition(pstate, c->location)));
 
-	newc->collOid = LookupCollation(pstate, c->collnames, c->location);
-	newc->collnames = c->collnames;
+	newc->collOid = LookupCollation(pstate, c->collname, c->location);
 	newc->location = c->location;
 
 	return (Node *) newc;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index c0eaea71a6649cea58ef1a922e8ea7dd94df0e88..fd1529fb3f9e5da9ab2e8975ea8b54cec48c888d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1583,7 +1583,7 @@ FigureColnameInternal(Node *node, char **name)
 			}
 			break;
 		case T_CollateClause:
-			return FigureColnameInternal((Node *) ((CollateClause *) node)->arg, name);
+			return FigureColnameInternal(((CollateClause *) node)->arg, name);
 		case T_CaseExpr:
 			strength = FigureColnameInternal((Node *) ((CaseExpr *) node)->defresult,
 											 name);
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 2ba9bf5181642d3f4896c25e14f58ab72800dc08..f413593f60220a6cc0e0080ded0836c6f310f513 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -471,7 +471,7 @@ GetColumnDefCollation(ParseState *pstate, ColumnDef *coldef, Oid typeOid)
 	{
 		/* We have a raw COLLATE clause, so look up the collation */
 		location = coldef->collClause->location;
-		result = LookupCollation(pstate, coldef->collClause->collnames,
+		result = LookupCollation(pstate, coldef->collClause->collname,
 								 location);
 	}
 	else if (OidIsValid(coldef->collOid))
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e876853af02c9a37b74e4031ea46a753898b4876..06baf89886a9dd08813f7a0e12571a48d1661809 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2467,7 +2467,7 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
 		Oid		collOid;
 
 		collOid = LookupCollation(cxt->pstate,
-								  column->collClause->collnames,
+								  column->collClause->collname,
 								  column->collClause->location);
 		/* Complain if COLLATE is applied to an uncollatable type */
 		if (!OidIsValid(typtup->typcollation))
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7cbd0222cb2e77b59ec3264c8ffabb68addeb1a5..ac0c53a7f320ed3522e8eb3dca75f6cf3e07dfd3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -226,6 +226,7 @@ static void get_coercion_expr(Node *arg, deparse_context *context,
 				  Node *parentNode);
 static void get_const_expr(Const *constval, deparse_context *context,
 			   int showtype);
+static void get_const_collation(Const *constval, deparse_context *context);
 static void simple_quote_literal(StringInfo buf, const char *val);
 static void get_sublink_expr(SubLink *sublink, deparse_context *context);
 static void get_from_clause(Query *query, const char *prefix,
@@ -5075,21 +5076,6 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
-		case T_CollateClause:
-			{
-				CollateClause *collate = (CollateClause *) node;
-				Node	   *arg = (Node *) collate->arg;
-
-				if (!PRETTY_PAREN(context))
-					appendStringInfoChar(buf, '(');
-				get_rule_expr_paren(arg, context, showimplicit, node);
-				appendStringInfo(buf, " COLLATE %s",
-								 generate_collation_name(collate->collOid));
-				if (!PRETTY_PAREN(context))
-					appendStringInfoChar(buf, ')');
-			}
-			break;
-
 		case T_CoerceViaIO:
 			{
 				CoerceViaIO *iocoerce = (CoerceViaIO *) node;
@@ -5152,6 +5138,21 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_CollateExpr:
+			{
+				CollateExpr *collate = (CollateExpr *) node;
+				Node	   *arg = (Node *) collate->arg;
+
+				if (!PRETTY_PAREN(context))
+					appendStringInfoChar(buf, '(');
+				get_rule_expr_paren(arg, context, showimplicit, node);
+				appendStringInfo(buf, " COLLATE %s",
+								 generate_collation_name(collate->collOid));
+				if (!PRETTY_PAREN(context))
+					appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		case T_CaseExpr:
 			{
 				CaseExpr   *caseexpr = (CaseExpr *) node;
@@ -5974,6 +5975,10 @@ get_coercion_expr(Node *arg, deparse_context *context,
  * showtype can be -1 to never show "::typename" decoration, or +1 to always
  * show it, or 0 to show it only if the constant wouldn't be assumed to be
  * the right type by default.
+ *
+ * If the Const's collation isn't default for its type, show that too.
+ * This can only happen in trees that have been through constant-folding.
+ * We assume we don't need to do this when showtype is -1.
  * ----------
  */
 static void
@@ -5994,9 +5999,12 @@ get_const_expr(Const *constval, deparse_context *context, int showtype)
 		 */
 		appendStringInfo(buf, "NULL");
 		if (showtype >= 0)
+		{
 			appendStringInfo(buf, "::%s",
 							 format_type_with_typemod(constval->consttype,
 													  constval->consttypmod));
+			get_const_collation(constval, context);
+		}
 		return;
 	}
 
@@ -6097,6 +6105,28 @@ get_const_expr(Const *constval, deparse_context *context, int showtype)
 		appendStringInfo(buf, "::%s",
 						 format_type_with_typemod(constval->consttype,
 												  constval->consttypmod));
+
+	get_const_collation(constval, context);
+}
+
+/*
+ * helper for get_const_expr: append COLLATE if needed
+ */
+static void
+get_const_collation(Const *constval, deparse_context *context)
+{
+	StringInfo	buf = context->buf;
+
+	if (OidIsValid(constval->constcollid))
+	{
+		Oid		typcollation = get_typcollation(constval->consttype);
+
+		if (constval->constcollid != typcollation)
+		{
+			appendStringInfo(buf, " COLLATE %s",
+							 generate_collation_name(constval->constcollid));
+		}
+	}
 }
 
 /*
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 27ce7955772e92038dd8b4bf0cac80864e297e7f..657015616cdd3bd1d86d49dd76d434b8e625fc13 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201103111
+#define CATALOG_VERSION_NO	201103112
 
 #endif
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cbaf123ee9d11a9914bd0df39cfeb86880e73778..8ed819b4dd40412e0447640457921f6813b021fc 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -148,6 +148,7 @@ typedef enum NodeTag
 	T_CoerceViaIO,
 	T_ArrayCoerceExpr,
 	T_ConvertRowtypeExpr,
+	T_CollateExpr,
 	T_CaseExpr,
 	T_CaseWhen,
 	T_CaseTestExpr,
@@ -169,7 +170,6 @@ typedef enum NodeTag
 	T_JoinExpr,
 	T_FromExpr,
 	T_IntoClause,
-	T_CollateClause,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -377,6 +377,7 @@ typedef enum NodeTag
 	T_A_ArrayExpr,
 	T_ResTarget,
 	T_TypeCast,
+	T_CollateClause,
 	T_SortBy,
 	T_WindowDef,
 	T_RangeSubselect,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9d4515cb27f39f5cae6165eb7385982a67aeb397..904bd5e9e18757a843bec97e275511fe1dbd88ec 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -265,6 +265,17 @@ typedef struct TypeCast
 	int			location;		/* token location, or -1 if unknown */
 } TypeCast;
 
+/*
+ * CollateClause - a COLLATE expression
+ */
+typedef struct CollateClause
+{
+	NodeTag		type;
+	Node	   *arg;			/* input expression */
+	List	   *collname;		/* possibly-qualified collation name */
+	int			location;		/* token location, or -1 if unknown */
+} CollateClause;
+
 /*
  * FuncCall - a function or aggregate invocation
  *
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 8c366df5f5619d27e80fb99011fc448425d202aa..41fd56e1bf13e78d9af3ca62712490a1f25160d2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -653,18 +653,6 @@ typedef struct RelabelType
 	int			location;		/* token location, or -1 if unknown */
 } RelabelType;
 
-/*
- * CollateClause - COLLATE
- */
-typedef struct CollateClause
-{
-	Expr		xpr;
-	Expr	   *arg;			/* original expression */
-	List	   *collnames;		/* assigned collation */
-	Oid			collOid;		/* resolved collation OID */
-	int			location;		/* token location, or -1 if unknown */
-} CollateClause;
-
 /* ----------------
  * CoerceViaIO
  *
@@ -730,6 +718,18 @@ typedef struct ConvertRowtypeExpr
 	int			location;		/* token location, or -1 if unknown */
 } ConvertRowtypeExpr;
 
+/*----------
+ * CollateExpr - COLLATE
+ *----------
+ */
+typedef struct CollateExpr
+{
+	Expr		xpr;
+	Expr	   *arg;			/* input expression */
+	Oid			collOid;		/* collation's OID */
+	int			location;		/* token location, or -1 if unknown */
+} CollateExpr;
+
 /*----------
  * CaseExpr - a CASE expression
  *
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 7af6eee088e3d7868f7a0e05a3a747eabf539f72..689686b7825656fa3078eb1676d24216d7b94e40 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5350,6 +5350,9 @@ exec_simple_check_node(Node *node)
 		case T_ConvertRowtypeExpr:
 			return exec_simple_check_node((Node *) ((ConvertRowtypeExpr *) node)->arg);
 
+		case T_CollateExpr:
+			return exec_simple_check_node((Node *) ((CollateExpr *) node)->arg);
+
 		case T_CaseExpr:
 			{
 				CaseExpr   *expr = (CaseExpr *) node;
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index 2102298abaa10e929c5b5da76d1cefcc9bdd5bc7..53595b6e10cfcad895f7bb1ec323f9e8f112b759 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -107,11 +107,11 @@ SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "C";
 
 SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en_US.utf8";
 ERROR:  collation mismatch between explicit collations "C" and "en_US.utf8"
-LINE 1: ...* FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "e...
+LINE 1: ...ELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLL...
                                                              ^
 SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en_US";
 ERROR:  collation mismatch between explicit collations "C" and "en_US"
-LINE 1: ...* FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "e...
+LINE 1: ...ELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLL...
                                                              ^
 CREATE DOMAIN testdomain_sv AS text COLLATE "sv_SE.utf8";
 CREATE DOMAIN testdomain_i AS int COLLATE "sv_SE.utf8"; -- fails