at3-exemptor.patch

text/plain

Filename: at3-exemptor.patch
Type: text/plain
Part: 0
Message: ALTER TYPE 3: add facility to identify further no-work cases

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
File+
doc/src/sgml/catalogs.sgml 10 0
doc/src/sgml/ref/create_cast.sgml 61 0
src/backend/commands/functioncmds.c 66 0
src/backend/nodes/copyfuncs.c 2 0
src/backend/nodes/equalfuncs.c 2 0
src/backend/nodes/makefuncs.c 1 0
src/backend/nodes/outfuncs.c 1 0
src/backend/nodes/readfuncs.c 1 0
src/backend/optimizer/util/clauses.c 2 0
src/backend/parser/gram.y 18 0
src/backend/parser/parse_coerce.c 99 0
src/backend/parser/parse_func.c 3 0
src/backend/utils/adt/ri_triggers.c 3 0
src/backend/utils/adt/varchar.c 14 0
src/backend/utils/adt/xml.c 7 0
src/bin/pg_dump/pg_dump.c 43 0
src/bin/pg_dump/pg_dump.h 1 0
src/include/catalog/catversion.h 1 1
src/include/catalog/pg_cast.h 199 0
src/include/catalog/pg_proc.h 4 0
src/include/nodes/parsenodes.h 1 0
src/include/nodes/primnodes.h 6 0
src/include/parser/kwlist.h 1 0
src/include/parser/parse_coerce.h 2 0
src/include/utils/builtins.h 1 0
src/include/utils/xml.h 1 0
src/test/regress/expected/alter_table.out 3 0
src/test/regress/expected/create_cast.out 36 0
src/test/regress/expected/opr_sanity.out 12 0
src/test/regress/expected/sanity_check.out 2 0
src/test/regress/output/misc.source 2 0
src/test/regress/sql/create_cast.sql 40 0
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 1419,1424 ****
--- 1419,1434 ----
       </row>
  
       <row>
+       <entry><structfield>castexemptor</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        The OID of the exemptor function used to identify when this cast is
+        unnecessary.  Zero is stored if the cast has no exemptor function.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>castcontext</structfield></entry>
        <entry><type>char</type></entry>
        <entry></entry>
*** a/doc/src/sgml/ref/create_cast.sgml
--- b/doc/src/sgml/ref/create_cast.sgml
***************
*** 20,25 ****
--- 20,26 ----
  <synopsis>
  CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
      WITH FUNCTION <replaceable>function_name</replaceable> (<replaceable>argument_type</replaceable> [, ...])
+     [ EXEMPTOR <replaceable>function_name</replaceable> (<replaceable>argument_type</replaceable> [, ...]) ]
      [ AS ASSIGNMENT | AS IMPLICIT ]
  
  CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
***************
*** 204,209 **** SELECT CAST ( 2 AS numeric ) + 4.0;
--- 205,221 ----
      </varlistentry>
  
      <varlistentry>
+      <term><literal>EXEMPTOR</literal> <replaceable>function_name</replaceable>(<replaceable>argument_type</replaceable> [, ...])</term>
+ 
+      <listitem>
+       <para>
+        The <firstterm>exemptor function</> used to identify when this cast is
+        unnecessary.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
+     <varlistentry>
       <term><literal>WITHOUT FUNCTION</literal></term>
  
       <listitem>
***************
*** 269,290 **** SELECT CAST ( 2 AS numeric ) + 4.0;
    </para>
  
    <para>
!    Ordinarily a cast must have different source and target data types.
!    However, it is allowed to declare a cast with identical source and
!    target types if it has a cast implementation function with more than one
!    argument.  This is used to represent type-specific length coercion
!    functions in the system catalogs.  The named function is used to
!    coerce a value of the type to the type modifier value given by its
!    second argument.
    </para>
  
    <para>
!    When a cast has different source and
!    target types and a function that takes more than one argument, it
!    supports converting from one type to another and applying a length
!    coercion in a single step.  When no such entry is available, coercion
!    to a type that uses a type modifier involves two cast steps, one to
!    convert between data types and a second to apply the modifier.
    </para>
  
   </refsect1>
--- 281,338 ----
    </para>
  
    <para>
!    Ordinarily a cast must have different source and target data types.  However,
!    it is allowed to declare a cast with identical source and target types if it
!    has a cast implementation function with more than one argument.  This is used
!    to represent type-specific length coercion functions in the system catalogs.
!    The named function is used to coerce a value of the type to the type modifier
!    value given by its second argument.
!   </para>
! 
!   <para>
!    When a cast has different source and target types and a function that takes
!    more than one argument, it supports converting from one type to another and
!    applying a length coercion in a single step.  When no such entry is
!    available, coercion to a type that uses a type modifier involves two cast
!    steps, one to convert between data types and a second to apply the modifier.
!    These casts may also define an exemptor function, but it is currently unused.
    </para>
  
    <para>
!    To minimize unnecessary calls its a cast function, the cast may specify an
!    exemptor function.  It must take three arguments: an <type>integer</> source
!    type modifier, an <type>integer</> destination type modifier, and
!    a <type>boolean</> indicating whether the cast is explicit.  Its result shall
!    be authoritative for all calls to the cast function with the given
!    destination type modifier, the given explicitness, and a value most-recently
!    coerced to the given source type modifier.  It returns an <type>integer</>,
!    which may contain the following bits:
! 
!    <variablelist>
!     <varlistentry>
!      <term><literal>COERCE_EXEMPT_NOCHANGE</> (<literal>1</>)</term>
!      <listitem>
!       <para>
! 	   If the cast function completes without error, its result will be
! 	   binary-equivalent to its input.
!       </para>
!      </listitem>
!     </varlistentry>
!     <varlistentry>
!      <term><literal>COERCE_EXEMPT_NOERROR</> (<literal>2</>)</term>
!      <listitem>
!       <para>
! 	   The cast function will not throw an error.
!       </para>
!      </listitem>
!     </varlistentry>
!    </variablelist>
! 
!    Consider a length coercion cast implementing a simple notion of length and
!    throwing an error when the input value is too long for the destination.  Its
!    exemptor function might return <literal>COERCE_EXEMPT_NOCHANGE |
!    COERCE_EXEMPT_NOERROR</> when the destination type modifier is at least as
!    large as the source type modifier.
    </para>
  
   </refsect1>
*** a/src/backend/commands/functioncmds.c
--- b/src/backend/commands/functioncmds.c
***************
*** 1487,1492 **** CreateCast(CreateCastStmt *stmt)
--- 1487,1493 ----
  	char		sourcetyptype;
  	char		targettyptype;
  	Oid			funcid;
+ 	Oid			exemptorid;
  	int			nargs;
  	char		castcontext;
  	char		castmethod;
***************
*** 1599,1604 **** CreateCast(CreateCastStmt *stmt)
--- 1600,1669 ----
  		nargs = 0;
  	}
  
+ 	/* Validate the exemptor function. */
+ 	if (stmt->exemptor != NULL)
+ 	{
+ 		Form_pg_proc procstruct;
+ 
+ 		if (nargs == 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 				  errmsg("cast function must take two or three arguments to use an exemptor function")));
+ 
+ 		exemptorid = LookupFuncNameTypeNames(stmt->exemptor->funcname,
+ 											 stmt->exemptor->funcargs,
+ 											 false);
+ 
+ 		tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(exemptorid));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for function %u", exemptorid);
+ 
+ 		procstruct = (Form_pg_proc) GETSTRUCT(tuple);
+ 		if (procstruct->pronargs != 3)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 				  errmsg("exemptor function must take three arguments")));
+ 		if (procstruct->proargtypes.values[0] != INT4OID)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("first argument of exemptor function must be type integer")));
+ 		if (procstruct->proargtypes.values[1] != INT4OID)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("second argument of exemptor function must be type integer")));
+ 		if (procstruct->proargtypes.values[2] != BOOLOID)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("third argument of exemptor function must be type boolean")));
+ 		if (procstruct->prorettype != INT4OID)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("return data type of exemptor function must be type integer")));
+ 		if (procstruct->provolatile == PROVOLATILE_VOLATILE)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("exemptor function must not be volatile")));
+ 
+ 		if (procstruct->proisagg)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 				 errmsg("exemptor function must not be an aggregate function")));
+ 		if (procstruct->proiswindow)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("exemptor function must not be a window function")));
+ 		if (procstruct->proretset)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("exemptor function must not return a set")));
+ 
+ 		ReleaseSysCache(tuple);
+ 	}
+ 	else
+ 	{
+ 		exemptorid = InvalidOid;
+ 	}
+ 
  	if (castmethod == COERCION_METHOD_BINARY)
  	{
  		int16		typ1len;
***************
*** 1725,1730 **** CreateCast(CreateCastStmt *stmt)
--- 1790,1796 ----
  	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
  	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
  	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
+ 	values[Anum_pg_cast_castexemptor - 1] = ObjectIdGetDatum(exemptorid);
  	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
  	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
  
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1133,1138 **** _copyFuncExpr(FuncExpr *from)
--- 1133,1139 ----
  	COPY_SCALAR_FIELD(funcresulttype);
  	COPY_SCALAR_FIELD(funcretset);
  	COPY_SCALAR_FIELD(funcformat);
+ 	COPY_SCALAR_FIELD(funcexempt);
  	COPY_NODE_FIELD(args);
  	COPY_LOCATION_FIELD(location);
  
***************
*** 3503,3508 **** _copyCreateCastStmt(CreateCastStmt *from)
--- 3504,3510 ----
  	COPY_NODE_FIELD(sourcetype);
  	COPY_NODE_FIELD(targettype);
  	COPY_NODE_FIELD(func);
+ 	COPY_NODE_FIELD(exemptor);
  	COPY_SCALAR_FIELD(context);
  	COPY_SCALAR_FIELD(inout);
  
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 236,241 **** _equalFuncExpr(FuncExpr *a, FuncExpr *b)
--- 236,242 ----
  		b->funcformat != COERCE_DONTCARE)
  		return false;
  
+ 	COMPARE_SCALAR_FIELD(funcexempt);
  	COMPARE_NODE_FIELD(args);
  	COMPARE_LOCATION_FIELD(location);
  
***************
*** 1883,1888 **** _equalCreateCastStmt(CreateCastStmt *a, CreateCastStmt *b)
--- 1884,1890 ----
  	COMPARE_NODE_FIELD(sourcetype);
  	COMPARE_NODE_FIELD(targettype);
  	COMPARE_NODE_FIELD(func);
+ 	COMPARE_NODE_FIELD(exemptor);
  	COMPARE_SCALAR_FIELD(context);
  	COMPARE_SCALAR_FIELD(inout);
  
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 447,452 **** makeFuncExpr(Oid funcid, Oid rettype, List *args, CoercionForm fformat)
--- 447,453 ----
  	funcexpr->funcresulttype = rettype;
  	funcexpr->funcretset = false;		/* only allowed case here */
  	funcexpr->funcformat = fformat;
+ 	funcexpr->funcexempt = 0;
  	funcexpr->args = args;
  	funcexpr->location = -1;
  
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 956,961 **** _outFuncExpr(StringInfo str, FuncExpr *node)
--- 956,962 ----
  	WRITE_OID_FIELD(funcresulttype);
  	WRITE_BOOL_FIELD(funcretset);
  	WRITE_ENUM_FIELD(funcformat, CoercionForm);
+ 	WRITE_INT_FIELD(funcexempt);
  	WRITE_NODE_FIELD(args);
  	WRITE_LOCATION_FIELD(location);
  }
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 525,530 **** _readFuncExpr(void)
--- 525,531 ----
  	READ_OID_FIELD(funcresulttype);
  	READ_BOOL_FIELD(funcretset);
  	READ_ENUM_FIELD(funcformat, CoercionForm);
+ 	READ_INT_FIELD(funcexempt);
  	READ_NODE_FIELD(args);
  	READ_LOCATION_FIELD(location);
  
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 2189,2194 **** eval_const_expressions_mutator(Node *node,
--- 2189,2195 ----
  		newexpr->funcresulttype = expr->funcresulttype;
  		newexpr->funcretset = expr->funcretset;
  		newexpr->funcformat = expr->funcformat;
+ 		newexpr->funcexempt = 0;
  		newexpr->args = args;
  		newexpr->location = expr->location;
  		return (Node *) newexpr;
***************
*** 3663,3668 **** evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args,
--- 3664,3670 ----
  	newexpr->funcresulttype = result_type;
  	newexpr->funcretset = false;
  	newexpr->funcformat = COERCE_DONTCARE;		/* doesn't matter */
+ 	newexpr->funcexempt = 0;
  	newexpr->args = args;
  	newexpr->location = -1;
  
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 489,495 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
  	DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
  
  	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
! 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 489,495 ----
  	DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
  
  	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
! 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXEMPTOR EXISTS EXPLAIN EXTERNAL EXTRACT
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
***************
*** 5811,5827 **** CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
--- 5811,5842 ----
  					n->sourcetype = $4;
  					n->targettype = $6;
  					n->func = $10;
+ 					n->exemptor = NULL;
  					n->context = (CoercionContext) $11;
  					n->inout = false;
  					$$ = (Node *)n;
  				}
  			| CREATE CAST '(' Typename AS Typename ')'
+ 					WITH FUNCTION function_with_argtypes
+ 					EXEMPTOR function_with_argtypes cast_context
+ 				{
+ 					CreateCastStmt *n = makeNode(CreateCastStmt);
+ 					n->sourcetype = $4;
+ 					n->targettype = $6;
+ 					n->func = $10;
+ 					n->exemptor = $12;
+ 					n->context = (CoercionContext) $13;
+ 					n->inout = false;
+ 					$$ = (Node *)n;
+ 				}
+ 			| CREATE CAST '(' Typename AS Typename ')'
  					WITHOUT FUNCTION cast_context
  				{
  					CreateCastStmt *n = makeNode(CreateCastStmt);
  					n->sourcetype = $4;
  					n->targettype = $6;
  					n->func = NULL;
+ 					n->exemptor = NULL;
  					n->context = (CoercionContext) $10;
  					n->inout = false;
  					$$ = (Node *)n;
***************
*** 5833,5838 **** CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
--- 5848,5854 ----
  					n->sourcetype = $4;
  					n->targettype = $6;
  					n->func = NULL;
+ 					n->exemptor = NULL;
  					n->context = (CoercionContext) $10;
  					n->inout = true;
  					$$ = (Node *)n;
***************
*** 11371,11376 **** unreserved_keyword:
--- 11387,11393 ----
  			| EXCLUDING
  			| EXCLUSIVE
  			| EXECUTE
+ 			| EXEMPTOR
  			| EXPLAIN
  			| EXTERNAL
  			| FAMILY
*** a/src/backend/parser/parse_coerce.c
--- b/src/backend/parser/parse_coerce.c
***************
*** 43,54 **** static Node *build_coercion_expression(Node *node,
  						  Oid funcId,
  						  Oid targetTypeId, int32 targetTypMod,
  						  CoercionForm cformat, int location,
! 						  bool isExplicit);
  static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
  						 Oid targetTypeId,
  						 CoercionContext ccontext,
  						 CoercionForm cformat,
  						 int location);
  static bool is_complex_array(Oid typid);
  static bool typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId);
  
--- 43,57 ----
  						  Oid funcId,
  						  Oid targetTypeId, int32 targetTypMod,
  						  CoercionForm cformat, int location,
! 						  bool isExplicit, CoerceExemptions exempt);
  static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
  						 Oid targetTypeId,
  						 CoercionContext ccontext,
  						 CoercionForm cformat,
  						 int location);
+ static CoerceExemptions apply_exemptor_function(Oid exemptId,
+ 						int32 sourceTypMod, int32 targetTypMod,
+ 						bool isExplicit, CoercionPathType *pathtype);
  static bool is_complex_array(Oid typid);
  static bool typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId);
  
***************
*** 91,97 **** coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
  						 ccontext, cformat, location);
  
  	/*
! 	 * If the target is a fixed-length type, it may need a length coercion as
  	 * well as a type coercion.  If we find ourselves adding both, force the
  	 * inner coercion node to implicit display form.
  	 */
--- 94,100 ----
  						 ccontext, cformat, location);
  
  	/*
! 	 * If the target is a variable-length type, it may need a length coercion as
  	 * well as a type coercion.  If we find ourselves adding both, force the
  	 * inner coercion node to implicit display form.
  	 */
***************
*** 129,137 **** coerce_type(ParseState *pstate, Node *node,
--- 132,144 ----
  			Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
  			CoercionContext ccontext, CoercionForm cformat, int location)
  {
+ 	Oid			baseTypeId;
+ 	int32		baseTypeMod;
  	Node	   *result;
  	CoercionPathType pathtype;
  	Oid			funcId;
+ 	Oid			exemptId;
+ 	CoerceExemptions exempt;
  
  	if (targetTypeId == inputTypeId ||
  		node == NULL)
***************
*** 182,189 **** coerce_type(ParseState *pstate, Node *node,
  		 */
  		Const	   *con = (Const *) node;
  		Const	   *newcon = makeNode(Const);
- 		Oid			baseTypeId;
- 		int32		baseTypeMod;
  		int32		inputTypeMod;
  		Type		targetType;
  		ParseCallbackState pcbstate;
--- 189,194 ----
***************
*** 278,306 **** coerce_type(ParseState *pstate, Node *node,
  		if (result)
  			return result;
  	}
  	pathtype = find_coercion_pathway(targetTypeId, inputTypeId, ccontext,
! 									 &funcId);
  	if (pathtype != COERCION_PATH_NONE)
  	{
  		if (pathtype != COERCION_PATH_RELABELTYPE)
  		{
  			/*
  			 * Generate an expression tree representing run-time application
! 			 * of the conversion function.	If we are dealing with a domain
! 			 * target type, the conversion function will yield the base type,
! 			 * and we need to extract the correct typmod to use from the
! 			 * domain's typtypmod.
  			 */
- 			Oid			baseTypeId;
- 			int32		baseTypeMod;
- 
- 			baseTypeMod = targetTypeMod;
- 			baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
- 
  			result = build_coercion_expression(node, pathtype, funcId,
  											   baseTypeId, baseTypeMod,
  											   cformat, location,
! 										  (cformat != COERCE_IMPLICIT_CAST));
  
  			/*
  			 * If domain, coerce to the domain type and relabel with domain
--- 283,322 ----
  		if (result)
  			return result;
  	}
+ 
+ 	/* Conventional case. */
  	pathtype = find_coercion_pathway(targetTypeId, inputTypeId, ccontext,
! 									 &funcId, &exemptId);
! 	if (pathtype != COERCION_PATH_NONE && pathtype != COERCION_PATH_RELABELTYPE)
! 	{
! 		/*
! 		 * If we are dealing with a domain target type, the conversion function
! 		 * will yield the base type, and we need to extract the correct typmod
! 		 * to use from the domain's typtypmod.
! 		 */
! 		baseTypeMod = targetTypeMod;
! 		baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
! 
! 		/* Look for available optimizations.  This may change the pathtype. */
! 		exempt = apply_exemptor_function(exemptId,
! 										 exprTypmod(node), baseTypeMod,
! 										 cformat != COERCE_IMPLICIT_CAST,
! 										 &pathtype);
! 	}
! 
  	if (pathtype != COERCION_PATH_NONE)
  	{
  		if (pathtype != COERCION_PATH_RELABELTYPE)
  		{
  			/*
  			 * Generate an expression tree representing run-time application
! 			 * of the conversion function.
  			 */
  			result = build_coercion_expression(node, pathtype, funcId,
  											   baseTypeId, baseTypeMod,
  											   cformat, location,
! 										  (cformat != COERCE_IMPLICIT_CAST),
! 											   exempt);
  
  			/*
  			 * If domain, coerce to the domain type and relabel with domain
***************
*** 418,423 **** can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids,
--- 434,440 ----
  		Oid			targetTypeId = target_typeids[i];
  		CoercionPathType pathtype;
  		Oid			funcId;
+ 		Oid			exemptId;
  
  		/* no problem if same type */
  		if (inputTypeId == targetTypeId)
***************
*** 446,452 **** can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids,
  		 * both binary-compatible and coercion-function cases.
  		 */
  		pathtype = find_coercion_pathway(targetTypeId, inputTypeId, ccontext,
! 										 &funcId);
  		if (pathtype != COERCION_PATH_NONE)
  			continue;
  
--- 463,469 ----
  		 * both binary-compatible and coercion-function cases.
  		 */
  		pathtype = find_coercion_pathway(targetTypeId, inputTypeId, ccontext,
! 										 &funcId, &exemptId);
  		if (pathtype != COERCION_PATH_NONE)
  			continue;
  
***************
*** 611,616 **** coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
--- 628,635 ----
  {
  	CoercionPathType pathtype;
  	Oid			funcId;
+ 	Oid			exemptId;
+ 	CoerceExemptions exempt;
  
  	/*
  	 * A negative typmod is assumed to mean that no coercion is wanted. Also,
***************
*** 619,627 **** coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
  	if (targetTypMod < 0 || targetTypMod == exprTypmod(node))
  		return node;
  
! 	pathtype = find_typmod_coercion_function(targetTypeId, &funcId);
  
! 	if (pathtype != COERCION_PATH_NONE)
  	{
  		/* Suppress display of nested coercion steps */
  		if (hideInputCoercion)
--- 638,648 ----
  	if (targetTypMod < 0 || targetTypMod == exprTypmod(node))
  		return node;
  
! 	pathtype = find_typmod_coercion_function(targetTypeId, &funcId, &exemptId);
! 	exempt = apply_exemptor_function(exemptId, exprTypmod(node), targetTypMod,
! 									 isExplicit, &pathtype);
  
! 	if (pathtype != COERCION_PATH_RELABELTYPE)
  	{
  		/* Suppress display of nested coercion steps */
  		if (hideInputCoercion)
***************
*** 630,636 **** coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
  		node = build_coercion_expression(node, pathtype, funcId,
  										 targetTypeId, targetTypMod,
  										 cformat, location,
! 										 isExplicit);
  	}
  
  	return node;
--- 651,657 ----
  		node = build_coercion_expression(node, pathtype, funcId,
  										 targetTypeId, targetTypMod,
  										 cformat, location,
! 										 isExplicit, exempt);
  	}
  
  	return node;
***************
*** 680,686 **** build_coercion_expression(Node *node,
  						  Oid funcId,
  						  Oid targetTypeId, int32 targetTypMod,
  						  CoercionForm cformat, int location,
! 						  bool isExplicit)
  {
  	int			nargs = 0;
  
--- 701,707 ----
  						  Oid funcId,
  						  Oid targetTypeId, int32 targetTypMod,
  						  CoercionForm cformat, int location,
! 						  bool isExplicit, CoerceExemptions exempt)
  {
  	int			nargs = 0;
  
***************
*** 751,756 **** build_coercion_expression(Node *node,
--- 772,778 ----
  		}
  
  		fexpr = makeFuncExpr(funcId, targetTypeId, args, cformat);
+ 		fexpr->funcexempt = exempt;
  		fexpr->location = location;
  		return (Node *) fexpr;
  	}
***************
*** 1821,1827 **** IsBinaryCoercible(Oid srctype, Oid targettype)
   *	3. A CoerceToDomain node inherits any always-noop invariant from its sole
   *		argument.  When GetDomainConstraints() == NIL, it also inherits
   *		never-error.  Otherwise, never-error becomes false.
!  *	4. All other nodes have neither invariant.
   *
   * Returns a bit string that may contain the following bits:
   *	COERCE_EXEMPT_NOCHANGE: expression result will always have the same binary
--- 1843,1853 ----
   *	3. A CoerceToDomain node inherits any always-noop invariant from its sole
   *		argument.  When GetDomainConstraints() == NIL, it also inherits
   *		never-error.  Otherwise, never-error becomes false.
!  *	4. A FuncExpr has the intersection of the invariants noted in its funcexempt
!  *		field and the invariants from its first argument.  These nodes will
!  *		represent calls to length coercion casts, so the first argument holds
!  *		the datum to coerce.
!  *	5. All other nodes have neither invariant.
   *
   * Returns a bit string that may contain the following bits:
   *	COERCE_EXEMPT_NOCHANGE: expression result will always have the same binary
***************
*** 1857,1862 **** GetCoerceExemptions(Node *expr,
--- 1883,1897 ----
  				ret &= ~COERCE_EXEMPT_NOERROR;
  			expr = (Node *) d->arg;
  		}
+ 		else if (IsA(expr, FuncExpr))
+ 		{
+ 			FuncExpr   *f = (FuncExpr *) expr;
+ 
+ 			ret &= f->funcexempt;
+ 			expr = linitial(f->args);
+ 			if (expr == NULL)
+ 				return 0;
+ 		}
  		else
  		{
  			return 0;
***************
*** 1893,1906 **** GetCoerceExemptions(Node *expr,
   * conversion then use IsBinaryCoercible().
   */
  CoercionPathType
! find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
  					  CoercionContext ccontext,
! 					  Oid *funcid)
  {
  	CoercionPathType result = COERCION_PATH_NONE;
  	HeapTuple	tuple;
  
  	*funcid = InvalidOid;
  
  	/* Perhaps the types are domains; if so, look at their base types */
  	if (OidIsValid(sourceTypeId))
--- 1928,1943 ----
   * conversion then use IsBinaryCoercible().
   */
  CoercionPathType
! find_coercion_pathway(Oid targetTypeId,
! 					  Oid sourceTypeId,
  					  CoercionContext ccontext,
! 					  Oid *funcid, Oid *exemptid)
  {
  	CoercionPathType result = COERCION_PATH_NONE;
  	HeapTuple	tuple;
  
  	*funcid = InvalidOid;
+ 	*exemptid = InvalidOid;
  
  	/* Perhaps the types are domains; if so, look at their base types */
  	if (OidIsValid(sourceTypeId))
***************
*** 1949,1954 **** find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
--- 1986,1992 ----
  				case COERCION_METHOD_FUNCTION:
  					result = COERCION_PATH_FUNC;
  					*funcid = castForm->castfunc;
+ 					*exemptid = castForm->castexemptor;
  					break;
  				case COERCION_METHOD_INOUT:
  					result = COERCION_PATH_COERCEVIAIO;
***************
*** 1992,2006 **** find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
  			{
  				CoercionPathType elempathtype;
  				Oid			elemfuncid;
  
  				elempathtype = find_coercion_pathway(targetElem,
  													 sourceElem,
  													 ccontext,
! 													 &elemfuncid);
  				if (elempathtype != COERCION_PATH_NONE &&
  					elempathtype != COERCION_PATH_ARRAYCOERCE)
  				{
  					*funcid = elemfuncid;
  					if (elempathtype == COERCION_PATH_COERCEVIAIO)
  						result = COERCION_PATH_COERCEVIAIO;
  					else
--- 2030,2047 ----
  			{
  				CoercionPathType elempathtype;
  				Oid			elemfuncid;
+ 				Oid			elemexemptorid;
  
  				elempathtype = find_coercion_pathway(targetElem,
  													 sourceElem,
  													 ccontext,
! 													 &elemfuncid,
! 													 &elemexemptorid);
  				if (elempathtype != COERCION_PATH_NONE &&
  					elempathtype != COERCION_PATH_ARRAYCOERCE)
  				{
  					*funcid = elemfuncid;
+ 					*exemptid = elemexemptorid;
  					if (elempathtype == COERCION_PATH_COERCEVIAIO)
  						result = COERCION_PATH_COERCEVIAIO;
  					else
***************
*** 2038,2044 **** find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
   * find_typmod_coercion_function -- does the given type need length coercion?
   *
   * If the target type possesses a pg_cast function from itself to itself,
!  * it must need length coercion.
   *
   * "bpchar" (ie, char(N)) and "numeric" are examples of such types.
   *
--- 2079,2085 ----
   * find_typmod_coercion_function -- does the given type need length coercion?
   *
   * If the target type possesses a pg_cast function from itself to itself,
!  * it needs length coercion at least some of the time.
   *
   * "bpchar" (ie, char(N)) and "numeric" are examples of such types.
   *
***************
*** 2050,2061 **** find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
   * We use the same result enum as find_coercion_pathway, but the only possible
   * result codes are:
   *	COERCION_PATH_NONE: no length coercion needed
!  *	COERCION_PATH_FUNC: apply the function returned in *funcid
   *	COERCION_PATH_ARRAYCOERCE: apply the function using ArrayCoerceExpr
   */
  CoercionPathType
  find_typmod_coercion_function(Oid typeId,
! 							  Oid *funcid)
  {
  	CoercionPathType result;
  	Type		targetType;
--- 2091,2103 ----
   * We use the same result enum as find_coercion_pathway, but the only possible
   * result codes are:
   *	COERCION_PATH_NONE: no length coercion needed
!  *	COERCION_PATH_FUNC: apply the function returned in *funcid, subject to
!  *		exemptions in *exempt
   *	COERCION_PATH_ARRAYCOERCE: apply the function using ArrayCoerceExpr
   */
  CoercionPathType
  find_typmod_coercion_function(Oid typeId,
! 							  Oid *funcid, Oid *exemptid)
  {
  	CoercionPathType result;
  	Type		targetType;
***************
*** 2063,2068 **** find_typmod_coercion_function(Oid typeId,
--- 2105,2111 ----
  	HeapTuple	tuple;
  
  	*funcid = InvalidOid;
+ 	*exemptid = InvalidOid;
  	result = COERCION_PATH_FUNC;
  
  	targetType = typeidType(typeId);
***************
*** 2087,2101 **** find_typmod_coercion_function(Oid typeId,
  		Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
  
  		*funcid = castForm->castfunc;
  		ReleaseSysCache(tuple);
  	}
  
  	if (!OidIsValid(*funcid))
! 		result = COERCION_PATH_NONE;
  
  	return result;
  }
  
  /*
   * is_complex_array
   *		Is this type an array of composite?
--- 2130,2171 ----
  		Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
  
  		*funcid = castForm->castfunc;
+ 		*exemptid = castForm->castexemptor;
  		ReleaseSysCache(tuple);
  	}
  
  	if (!OidIsValid(*funcid))
! 		result = COERCION_PATH_RELABELTYPE;
  
  	return result;
  }
  
+ 
+ static CoerceExemptions
+ apply_exemptor_function(Oid exemptId,
+ 						int32 sourceTypMod, int32 targetTypMod,
+ 						bool isExplicit, CoercionPathType *pathtype)
+ {
+ 	CoerceExemptions ret = 0;
+ 
+ 	/*
+ 	 * Call the exemptor function, if any; its verdict may let us skip calls to
+ 	 * the length coercion function entirely.
+ 	 */
+ 	if (OidIsValid(exemptId))
+ 	{
+ 		ret = DatumGetInt32(OidFunctionCall3(exemptId,
+ 											 Int32GetDatum(sourceTypMod),
+ 											 Int32GetDatum(targetTypMod),
+ 											 BoolGetDatum(isExplicit)));
+ 		if (ret == (COERCE_EXEMPT_NOCHANGE | COERCE_EXEMPT_NOERROR))
+ 			*pathtype = COERCION_PATH_RELABELTYPE;
+ 	}
+ 
+ 	return ret;
+ }
+ 
+ 
  /*
   * is_complex_array
   *		Is this type an array of composite?
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 382,387 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 382,388 ----
  		funcexpr->funcresulttype = rettype;
  		funcexpr->funcretset = retset;
  		funcexpr->funcformat = COERCE_EXPLICIT_CALL;
+ 		funcexpr->funcexempt = 0;
  		funcexpr->args = fargs;
  		funcexpr->location = location;
  
***************
*** 1018,1027 **** func_get_detail(List *funcname,
  				{
  					CoercionPathType cpathtype;
  					Oid			cfuncid;
  
  					cpathtype = find_coercion_pathway(targetType, sourceType,
  													  COERCION_EXPLICIT,
! 													  &cfuncid);
  					switch (cpathtype)
  					{
  						case COERCION_PATH_RELABELTYPE:
--- 1019,1029 ----
  				{
  					CoercionPathType cpathtype;
  					Oid			cfuncid;
+ 					Oid			cexemptid;
  
  					cpathtype = find_coercion_pathway(targetType, sourceType,
  													  COERCION_EXPLICIT,
! 													  &cfuncid, &cexemptid);
  					switch (cpathtype)
  					{
  						case COERCION_PATH_RELABELTYPE:
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 3950,3956 **** ri_HashCompareOp(Oid eq_opr, Oid typeid)
  	{
  		Oid			lefttype,
  					righttype,
! 					castfunc;
  		CoercionPathType pathtype;
  
  		/* We always need to know how to call the equality operator */
--- 3950,3957 ----
  	{
  		Oid			lefttype,
  					righttype,
! 					castfunc,
! 					castexempt;
  		CoercionPathType pathtype;
  
  		/* We always need to know how to call the equality operator */
***************
*** 3977,3983 **** ri_HashCompareOp(Oid eq_opr, Oid typeid)
  		{
  			pathtype = find_coercion_pathway(lefttype, typeid,
  											 COERCION_IMPLICIT,
! 											 &castfunc);
  			if (pathtype != COERCION_PATH_FUNC &&
  				pathtype != COERCION_PATH_RELABELTYPE)
  			{
--- 3978,3984 ----
  		{
  			pathtype = find_coercion_pathway(lefttype, typeid,
  											 COERCION_IMPLICIT,
! 											 &castfunc, &castexempt);
  			if (pathtype != COERCION_PATH_FUNC &&
  				pathtype != COERCION_PATH_RELABELTYPE)
  			{
*** a/src/backend/utils/adt/varchar.c
--- b/src/backend/utils/adt/varchar.c
***************
*** 549,554 **** varcharsend(PG_FUNCTION_ARGS)
--- 549,568 ----
  
  
  /*
+  * Identify superfluous calls to our length coercion function.
+  */
+ Datum
+ varchar_exemptor(PG_FUNCTION_ARGS)
+ {
+ 	int32 old_max = PG_GETARG_INT32(0) - VARHDRSZ;
+ 	int32 new_max = PG_GETARG_INT32(1) - VARHDRSZ;
+ 
+ 	if (new_max < 0 || (old_max >= 0 && old_max <= new_max))
+ 		PG_RETURN_INT32(COERCE_EXEMPT_NOCHANGE | COERCE_EXEMPT_NOERROR);
+ 	PG_RETURN_INT32(0);
+ }
+ 
+ /*
   * Converts a VARCHAR type to the specified size.
   *
   * maxlen is the typmod, ie, declared length plus VARHDRSZ bytes.
*** a/src/backend/utils/adt/xml.c
--- b/src/backend/utils/adt/xml.c
***************
*** 508,513 **** xmlconcat2(PG_FUNCTION_ARGS)
--- 508,520 ----
  }
  
  
+ /* texttoxml can only throw an error, never change the data. */
+ Datum
+ xml_exemptor(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_INT32(COERCE_EXEMPT_NOCHANGE);
+ }
+ 
  Datum
  texttoxml(PG_FUNCTION_ARGS)
  {
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 5031,5054 **** getCasts(int *numCasts)
  	int			i_castsource;
  	int			i_casttarget;
  	int			i_castfunc;
  	int			i_castcontext;
  	int			i_castmethod;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
  						  "castsource, casttarget, castfunc, castcontext, "
! 						  "castmethod "
  						  "FROM pg_cast ORDER BY 3,4");
  	}
  	else if (g_fout->remoteVersion >= 70300)
  	{
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
  						  "castsource, casttarget, castfunc, castcontext, "
! 				"CASE WHEN castfunc = 0 THEN 'b' ELSE 'f' END AS castmethod "
  						  "FROM pg_cast ORDER BY 3,4");
  	}
  	else
--- 5031,5063 ----
  	int			i_castsource;
  	int			i_casttarget;
  	int			i_castfunc;
+ 	int			i_castexemptor;
  	int			i_castcontext;
  	int			i_castmethod;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90100)
! 	{
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "castsource, casttarget, castfunc, castcontext, "
! 						  "castmethod, castexemptor "
! 						  "FROM pg_cast ORDER BY 3,4");
! 	}
! 	else if (g_fout->remoteVersion >= 80400)
  	{
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
  						  "castsource, casttarget, castfunc, castcontext, "
! 						  "castmethod, 0 AS castexemptor "
  						  "FROM pg_cast ORDER BY 3,4");
  	}
  	else if (g_fout->remoteVersion >= 70300)
  	{
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
  						  "castsource, casttarget, castfunc, castcontext, "
! 				"CASE WHEN castfunc = 0 THEN 'b' ELSE 'f' END AS castmethod, "
! 						  "0 AS castexemptor "
  						  "FROM pg_cast ORDER BY 3,4");
  	}
  	else
***************
*** 5056,5062 **** getCasts(int *numCasts)
  		appendPQExpBuffer(query, "SELECT 0 AS tableoid, p.oid, "
  						  "t1.oid AS castsource, t2.oid AS casttarget, "
  						  "p.oid AS castfunc, 'e' AS castcontext, "
! 						  "'f' AS castmethod "
  						  "FROM pg_type t1, pg_type t2, pg_proc p "
  						  "WHERE p.pronargs = 1 AND "
  						  "p.proargtypes[0] = t1.oid AND "
--- 5065,5072 ----
  		appendPQExpBuffer(query, "SELECT 0 AS tableoid, p.oid, "
  						  "t1.oid AS castsource, t2.oid AS casttarget, "
  						  "p.oid AS castfunc, 'e' AS castcontext, "
! 						  "'f' AS castmethod, "
! 						  "0 AS castexemptor "
  						  "FROM pg_type t1, pg_type t2, pg_proc p "
  						  "WHERE p.pronargs = 1 AND "
  						  "p.proargtypes[0] = t1.oid AND "
***************
*** 5078,5083 **** getCasts(int *numCasts)
--- 5088,5094 ----
  	i_castsource = PQfnumber(res, "castsource");
  	i_casttarget = PQfnumber(res, "casttarget");
  	i_castfunc = PQfnumber(res, "castfunc");
+ 	i_castexemptor = PQfnumber(res, "castexemptor");
  	i_castcontext = PQfnumber(res, "castcontext");
  	i_castmethod = PQfnumber(res, "castmethod");
  
***************
*** 5094,5099 **** getCasts(int *numCasts)
--- 5105,5111 ----
  		castinfo[i].castsource = atooid(PQgetvalue(res, i, i_castsource));
  		castinfo[i].casttarget = atooid(PQgetvalue(res, i, i_casttarget));
  		castinfo[i].castfunc = atooid(PQgetvalue(res, i, i_castfunc));
+ 		castinfo[i].castexemptor = atooid(PQgetvalue(res, i, i_castexemptor));
  		castinfo[i].castcontext = *(PQgetvalue(res, i, i_castcontext));
  		castinfo[i].castmethod = *(PQgetvalue(res, i, i_castmethod));
  
***************
*** 5124,5129 **** getCasts(int *numCasts)
--- 5136,5142 ----
  				addObjectDependency(&castinfo[i].dobj,
  									funcInfo->dobj.dumpId);
  		}
+ 		/* Exemptor functions postdate the dependency mechanism. */
  	}
  
  	PQclear(res);
***************
*** 8330,8335 **** dumpCast(Archive *fout, CastInfo *cast)
--- 8343,8349 ----
  	PQExpBuffer delqry;
  	PQExpBuffer castsig;
  	FuncInfo   *funcInfo = NULL;
+ 	FuncInfo   *exemptInfo = NULL;
  	TypeInfo   *sourceInfo;
  	TypeInfo   *targetInfo;
  
***************
*** 8340,8345 **** dumpCast(Archive *fout, CastInfo *cast)
--- 8354,8367 ----
  	{
  		funcInfo = findFuncByOid(cast->castfunc);
  		if (funcInfo == NULL)
+ 			/* XXX: is silently bailing correct? */
+ 			return;
+ 	}
+ 	if (OidIsValid(cast->castexemptor))
+ 	{
+ 		exemptInfo = findFuncByOid(cast->castexemptor);
+ 		if (exemptInfo == NULL)
+ 			/* XXX: is silently bailing correct? */
  			return;
  	}
  
***************
*** 8360,8375 **** dumpCast(Archive *fout, CastInfo *cast)
  	 */
  	if ((funcInfo == NULL ||
  		 strncmp(funcInfo->dobj.namespace->dobj.name, "pg_", 3) == 0) &&
  		strncmp(sourceInfo->dobj.namespace->dobj.name, "pg_", 3) == 0 &&
  		strncmp(targetInfo->dobj.namespace->dobj.name, "pg_", 3) == 0)
  		return;
  
  	/*
! 	 * Skip cast if function isn't from pg_ and is not to be dumped.
  	 */
! 	if (funcInfo &&
! 		strncmp(funcInfo->dobj.namespace->dobj.name, "pg_", 3) != 0 &&
! 		!funcInfo->dobj.dump)
  		return;
  
  	/*
--- 8382,8402 ----
  	 */
  	if ((funcInfo == NULL ||
  		 strncmp(funcInfo->dobj.namespace->dobj.name, "pg_", 3) == 0) &&
+ 		(exemptInfo == NULL ||
+ 		 strncmp(exemptInfo->dobj.namespace->dobj.name, "pg_", 3) == 0) &&
  		strncmp(sourceInfo->dobj.namespace->dobj.name, "pg_", 3) == 0 &&
  		strncmp(targetInfo->dobj.namespace->dobj.name, "pg_", 3) == 0)
  		return;
  
  	/*
! 	 * Skip cast if a function isn't from pg_ and is not to be dumped.
  	 */
! 	if ((funcInfo &&
! 		 strncmp(funcInfo->dobj.namespace->dobj.name, "pg_", 3) != 0 &&
! 		 !funcInfo->dobj.dump) ||
! 		(exemptInfo &&
! 		 strncmp(exemptInfo->dobj.namespace->dobj.name, "pg_", 3) != 0 &&
! 		 !exemptInfo->dobj.dump))
  		return;
  
  	/*
***************
*** 8419,8424 **** dumpCast(Archive *fout, CastInfo *cast)
--- 8446,8459 ----
  							  fmtId(funcInfo->dobj.namespace->dobj.name));
  			appendPQExpBuffer(defqry, "%s",
  							  format_function_signature(funcInfo, true));
+ 
+ 			if (exemptInfo != NULL)
+ 			{
+ 				appendPQExpBuffer(defqry, " EXEMPTOR %s.",
+ 								  fmtId(exemptInfo->dobj.namespace->dobj.name));
+ 				appendPQExpBuffer(defqry, "%s",
+ 								  format_function_signature(exemptInfo, true));
+ 			}
  			break;
  		default:
  			write_msg(NULL, "WARNING: bogus value in pg_cast.castmethod field\n");
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 373,378 **** typedef struct _castInfo
--- 373,379 ----
  	Oid			castsource;
  	Oid			casttarget;
  	Oid			castfunc;
+ 	Oid			castexemptor;
  	char		castcontext;
  	char		castmethod;
  } CastInfo;
*** a/src/include/catalog/catversion.h
--- b/src/include/catalog/catversion.h
***************
*** 53,58 ****
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	201101081
  
  #endif
--- 53,58 ----
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	201101101
  
  #endif
*** a/src/include/catalog/pg_cast.h
--- b/src/include/catalog/pg_cast.h
***************
*** 35,40 **** CATALOG(pg_cast,2605)
--- 35,41 ----
  	Oid			castsource;		/* source datatype for cast */
  	Oid			casttarget;		/* destination datatype for cast */
  	Oid			castfunc;		/* cast function; 0 = binary coercible */
+ 	Oid			castexemptor;	/* exemptor function; 0 = none */
  	char		castcontext;	/* contexts in which cast can be used */
  	char		castmethod;		/* cast method */
  } FormData_pg_cast;
***************
*** 74,85 **** typedef enum CoercionMethod
   *		compiler constants for pg_cast
   * ----------------
   */
! #define Natts_pg_cast				5
  #define Anum_pg_cast_castsource		1
  #define Anum_pg_cast_casttarget		2
  #define Anum_pg_cast_castfunc		3
! #define Anum_pg_cast_castcontext	4
! #define Anum_pg_cast_castmethod		5
  
  /* ----------------
   *		initial contents of pg_cast
--- 75,87 ----
   *		compiler constants for pg_cast
   * ----------------
   */
! #define Natts_pg_cast				6
  #define Anum_pg_cast_castsource		1
  #define Anum_pg_cast_casttarget		2
  #define Anum_pg_cast_castfunc		3
! #define Anum_pg_cast_castexemptor	4
! #define Anum_pg_cast_castcontext	5
! #define Anum_pg_cast_castmethod		6
  
  /* ----------------
   *		initial contents of pg_cast
***************
*** 94,135 **** typedef enum CoercionMethod
   * int2->int4->int8->numeric->float4->float8, while casts in the
   * reverse direction are assignment-only.
   */
! DATA(insert (	20	 21  714 a f ));
! DATA(insert (	20	 23  480 a f ));
! DATA(insert (	20	700  652 i f ));
! DATA(insert (	20	701  482 i f ));
! DATA(insert (	20 1700 1781 i f ));
! DATA(insert (	21	 20  754 i f ));
! DATA(insert (	21	 23  313 i f ));
! DATA(insert (	21	700  236 i f ));
! DATA(insert (	21	701  235 i f ));
! DATA(insert (	21 1700 1782 i f ));
! DATA(insert (	23	 20  481 i f ));
! DATA(insert (	23	 21  314 a f ));
! DATA(insert (	23	700  318 i f ));
! DATA(insert (	23	701  316 i f ));
! DATA(insert (	23 1700 1740 i f ));
! DATA(insert (  700	 20  653 a f ));
! DATA(insert (  700	 21  238 a f ));
! DATA(insert (  700	 23  319 a f ));
! DATA(insert (  700	701  311 i f ));
! DATA(insert (  700 1700 1742 a f ));
! DATA(insert (  701	 20  483 a f ));
! DATA(insert (  701	 21  237 a f ));
! DATA(insert (  701	 23  317 a f ));
! DATA(insert (  701	700  312 a f ));
! DATA(insert (  701 1700 1743 a f ));
! DATA(insert ( 1700	 20 1779 a f ));
! DATA(insert ( 1700	 21 1783 a f ));
! DATA(insert ( 1700	 23 1744 a f ));
! DATA(insert ( 1700	700 1745 i f ));
! DATA(insert ( 1700	701 1746 i f ));
! DATA(insert (  790 1700 3823 a f ));
! DATA(insert ( 1700	790 3824 a f ));
  
  /* Allow explicit coercions between int4 and bool */
! DATA(insert (	23	16	2557 e f ));
! DATA(insert (	16	23	2558 e f ));
  
  /*
   * OID category: allow implicit conversion from any integral type (including
--- 96,137 ----
   * int2->int4->int8->numeric->float4->float8, while casts in the
   * reverse direction are assignment-only.
   */
! DATA(insert (	20	 21  714 0 a f ));
! DATA(insert (	20	 23  480 0 a f ));
! DATA(insert (	20	700  652 0 i f ));
! DATA(insert (	20	701  482 0 i f ));
! DATA(insert (	20 1700 1781 0 i f ));
! DATA(insert (	21	 20  754 0 i f ));
! DATA(insert (	21	 23  313 0 i f ));
! DATA(insert (	21	700  236 0 i f ));
! DATA(insert (	21	701  235 0 i f ));
! DATA(insert (	21 1700 1782 0 i f ));
! DATA(insert (	23	 20  481 0 i f ));
! DATA(insert (	23	 21  314 0 a f ));
! DATA(insert (	23	700  318 0 i f ));
! DATA(insert (	23	701  316 0 i f ));
! DATA(insert (	23 1700 1740 0 i f ));
! DATA(insert (  700	 20  653 0 a f ));
! DATA(insert (  700	 21  238 0 a f ));
! DATA(insert (  700	 23  319 0 a f ));
! DATA(insert (  700	701  311 0 i f ));
! DATA(insert (  700 1700 1742 0 a f ));
! DATA(insert (  701	 20  483 0 a f ));
! DATA(insert (  701	 21  237 0 a f ));
! DATA(insert (  701	 23  317 0 a f ));
! DATA(insert (  701	700  312 0 a f ));
! DATA(insert (  701 1700 1743 0 a f ));
! DATA(insert ( 1700	 20 1779 0 a f ));
! DATA(insert ( 1700	 21 1783 0 a f ));
! DATA(insert ( 1700	 23 1744 0 a f ));
! DATA(insert ( 1700	700 1745 0 i f ));
! DATA(insert ( 1700	701 1746 0 i f ));
! DATA(insert (  790 1700 3823 0 a f ));
! DATA(insert ( 1700	790 3824 0 a f ));
  
  /* Allow explicit coercions between int4 and bool */
! DATA(insert (	23	16	2557 0 e f ));
! DATA(insert (	16	23	2558 0 e f ));
  
  /*
   * OID category: allow implicit conversion from any integral type (including
***************
*** 141,307 **** DATA(insert (	16	23	2558 e f ));
   * casts from text and varchar to regclass, which exist mainly to support
   * legacy forms of nextval() and related functions.
   */
! DATA(insert (	20	 26 1287 i f ));
! DATA(insert (	21	 26  313 i f ));
! DATA(insert (	23	 26    0 i b ));
! DATA(insert (	26	 20 1288 a f ));
! DATA(insert (	26	 23    0 a b ));
! DATA(insert (	26	 24    0 i b ));
! DATA(insert (	24	 26    0 i b ));
! DATA(insert (	20	 24 1287 i f ));
! DATA(insert (	21	 24  313 i f ));
! DATA(insert (	23	 24    0 i b ));
! DATA(insert (	24	 20 1288 a f ));
! DATA(insert (	24	 23    0 a b ));
! DATA(insert (	24 2202    0 i b ));
! DATA(insert ( 2202	 24    0 i b ));
! DATA(insert (	26 2202    0 i b ));
! DATA(insert ( 2202	 26    0 i b ));
! DATA(insert (	20 2202 1287 i f ));
! DATA(insert (	21 2202  313 i f ));
! DATA(insert (	23 2202    0 i b ));
! DATA(insert ( 2202	 20 1288 a f ));
! DATA(insert ( 2202	 23    0 a b ));
! DATA(insert (	26 2203    0 i b ));
! DATA(insert ( 2203	 26    0 i b ));
! DATA(insert (	20 2203 1287 i f ));
! DATA(insert (	21 2203  313 i f ));
! DATA(insert (	23 2203    0 i b ));
! DATA(insert ( 2203	 20 1288 a f ));
! DATA(insert ( 2203	 23    0 a b ));
! DATA(insert ( 2203 2204    0 i b ));
! DATA(insert ( 2204 2203    0 i b ));
! DATA(insert (	26 2204    0 i b ));
! DATA(insert ( 2204	 26    0 i b ));
! DATA(insert (	20 2204 1287 i f ));
! DATA(insert (	21 2204  313 i f ));
! DATA(insert (	23 2204    0 i b ));
! DATA(insert ( 2204	 20 1288 a f ));
! DATA(insert ( 2204	 23    0 a b ));
! DATA(insert (	26 2205    0 i b ));
! DATA(insert ( 2205	 26    0 i b ));
! DATA(insert (	20 2205 1287 i f ));
! DATA(insert (	21 2205  313 i f ));
! DATA(insert (	23 2205    0 i b ));
! DATA(insert ( 2205	 20 1288 a f ));
! DATA(insert ( 2205	 23    0 a b ));
! DATA(insert (	26 2206    0 i b ));
! DATA(insert ( 2206	 26    0 i b ));
! DATA(insert (	20 2206 1287 i f ));
! DATA(insert (	21 2206  313 i f ));
! DATA(insert (	23 2206    0 i b ));
! DATA(insert ( 2206	 20 1288 a f ));
! DATA(insert ( 2206	 23    0 a b ));
! DATA(insert (	26 3734    0 i b ));
! DATA(insert ( 3734	 26    0 i b ));
! DATA(insert (	20 3734 1287 i f ));
! DATA(insert (	21 3734  313 i f ));
! DATA(insert (	23 3734    0 i b ));
! DATA(insert ( 3734	 20 1288 a f ));
! DATA(insert ( 3734	 23    0 a b ));
! DATA(insert (	26 3769    0 i b ));
! DATA(insert ( 3769	 26    0 i b ));
! DATA(insert (	20 3769 1287 i f ));
! DATA(insert (	21 3769  313 i f ));
! DATA(insert (	23 3769    0 i b ));
! DATA(insert ( 3769	 20 1288 a f ));
! DATA(insert ( 3769	 23    0 a b ));
! DATA(insert (	25 2205 1079 i f ));
! DATA(insert ( 1043 2205 1079 i f ));
  
  /*
   * String category
   */
! DATA(insert (	25 1042    0 i b ));
! DATA(insert (	25 1043    0 i b ));
! DATA(insert ( 1042	 25  401 i f ));
! DATA(insert ( 1042 1043  401 i f ));
! DATA(insert ( 1043	 25    0 i b ));
! DATA(insert ( 1043 1042    0 i b ));
! DATA(insert (	18	 25  946 i f ));
! DATA(insert (	18 1042  860 a f ));
! DATA(insert (	18 1043  946 a f ));
! DATA(insert (	19	 25  406 i f ));
! DATA(insert (	19 1042  408 a f ));
! DATA(insert (	19 1043 1401 a f ));
! DATA(insert (	25	 18  944 a f ));
! DATA(insert ( 1042	 18  944 a f ));
! DATA(insert ( 1043	 18  944 a f ));
! DATA(insert (	25	 19  407 i f ));
! DATA(insert ( 1042	 19  409 i f ));
! DATA(insert ( 1043	 19 1400 i f ));
  
  /* Allow explicit coercions between int4 and "char" */
! DATA(insert (	18	 23   77 e f ));
! DATA(insert (	23	 18   78 e f ));
  
  /* pg_node_tree can be coerced to, but not from, text */
! DATA(insert (  194	 25    0 i b ));
  
  /*
   * Datetime category
   */
! DATA(insert (  702 1082 1179 a f ));
! DATA(insert (  702 1083 1364 a f ));
! DATA(insert (  702 1114 2023 i f ));
! DATA(insert (  702 1184 1173 i f ));
! DATA(insert (  703 1186 1177 i f ));
! DATA(insert ( 1082 1114 2024 i f ));
! DATA(insert ( 1082 1184 1174 i f ));
! DATA(insert ( 1083 1186 1370 i f ));
! DATA(insert ( 1083 1266 2047 i f ));
! DATA(insert ( 1114	702 2030 a f ));
! DATA(insert ( 1114 1082 2029 a f ));
! DATA(insert ( 1114 1083 1316 a f ));
! DATA(insert ( 1114 1184 2028 i f ));
! DATA(insert ( 1184	702 1180 a f ));
! DATA(insert ( 1184 1082 1178 a f ));
! DATA(insert ( 1184 1083 2019 a f ));
! DATA(insert ( 1184 1114 2027 a f ));
! DATA(insert ( 1184 1266 1388 a f ));
! DATA(insert ( 1186	703 1194 a f ));
! DATA(insert ( 1186 1083 1419 a f ));
! DATA(insert ( 1266 1083 2046 a f ));
  /* Cross-category casts between int4 and abstime, reltime */
! DATA(insert (	23	702    0 e b ));
! DATA(insert (  702	 23    0 e b ));
! DATA(insert (	23	703    0 e b ));
! DATA(insert (  703	 23    0 e b ));
  
  /*
   * Geometric category
   */
! DATA(insert (  601	600 1532 e f ));
! DATA(insert (  602	600 1533 e f ));
! DATA(insert (  602	604 1449 a f ));
! DATA(insert (  603	600 1534 e f ));
! DATA(insert (  603	601 1541 e f ));
! DATA(insert (  603	604 1448 a f ));
! DATA(insert (  603	718 1479 e f ));
! DATA(insert (  604	600 1540 e f ));
! DATA(insert (  604	602 1447 a f ));
! DATA(insert (  604	603 1446 e f ));
! DATA(insert (  604	718 1474 e f ));
! DATA(insert (  718	600 1416 e f ));
! DATA(insert (  718	603 1480 e f ));
! DATA(insert (  718	604 1544 e f ));
  
  /*
   * INET category
   */
! DATA(insert (  650	869    0 i b ));
! DATA(insert (  869	650 1715 a f ));
  
  /*
   * BitString category
   */
! DATA(insert ( 1560 1562    0 i b ));
! DATA(insert ( 1562 1560    0 i b ));
  /* Cross-category casts between bit and int4, int8 */
! DATA(insert (	20 1560 2075 e f ));
! DATA(insert (	23 1560 1683 e f ));
! DATA(insert ( 1560	 20 2076 e f ));
! DATA(insert ( 1560	 23 1684 e f ));
  
  /*
   * Cross-category casts to and from TEXT
--- 143,309 ----
   * casts from text and varchar to regclass, which exist mainly to support
   * legacy forms of nextval() and related functions.
   */
! DATA(insert (	20	 26 1287 0 i f ));
! DATA(insert (	21	 26  313 0 i f ));
! DATA(insert (	23	 26    0 0 i b ));
! DATA(insert (	26	 20 1288 0 a f ));
! DATA(insert (	26	 23    0 0 a b ));
! DATA(insert (	26	 24    0 0 i b ));
! DATA(insert (	24	 26    0 0 i b ));
! DATA(insert (	20	 24 1287 0 i f ));
! DATA(insert (	21	 24  313 0 i f ));
! DATA(insert (	23	 24    0 0 i b ));
! DATA(insert (	24	 20 1288 0 a f ));
! DATA(insert (	24	 23    0 0 a b ));
! DATA(insert (	24 2202    0 0 i b ));
! DATA(insert ( 2202	 24    0 0 i b ));
! DATA(insert (	26 2202    0 0 i b ));
! DATA(insert ( 2202	 26    0 0 i b ));
! DATA(insert (	20 2202 1287 0 i f ));
! DATA(insert (	21 2202  313 0 i f ));
! DATA(insert (	23 2202    0 0 i b ));
! DATA(insert ( 2202	 20 1288 0 a f ));
! DATA(insert ( 2202	 23    0 0 a b ));
! DATA(insert (	26 2203    0 0 i b ));
! DATA(insert ( 2203	 26    0 0 i b ));
! DATA(insert (	20 2203 1287 0 i f ));
! DATA(insert (	21 2203  313 0 i f ));
! DATA(insert (	23 2203    0 0 i b ));
! DATA(insert ( 2203	 20 1288 0 a f ));
! DATA(insert ( 2203	 23    0 0 a b ));
! DATA(insert ( 2203 2204    0 0 i b ));
! DATA(insert ( 2204 2203    0 0 i b ));
! DATA(insert (	26 2204    0 0 i b ));
! DATA(insert ( 2204	 26    0 0 i b ));
! DATA(insert (	20 2204 1287 0 i f ));
! DATA(insert (	21 2204  313 0 i f ));
! DATA(insert (	23 2204    0 0 i b ));
! DATA(insert ( 2204	 20 1288 0 a f ));
! DATA(insert ( 2204	 23    0 0 a b ));
! DATA(insert (	26 2205    0 0 i b ));
! DATA(insert ( 2205	 26    0 0 i b ));
! DATA(insert (	20 2205 1287 0 i f ));
! DATA(insert (	21 2205  313 0 i f ));
! DATA(insert (	23 2205    0 0 i b ));
! DATA(insert ( 2205	 20 1288 0 a f ));
! DATA(insert ( 2205	 23    0 0 a b ));
! DATA(insert (	26 2206    0 0 i b ));
! DATA(insert ( 2206	 26    0 0 i b ));
! DATA(insert (	20 2206 1287 0 i f ));
! DATA(insert (	21 2206  313 0 i f ));
! DATA(insert (	23 2206    0 0 i b ));
! DATA(insert ( 2206	 20 1288 0 a f ));
! DATA(insert ( 2206	 23    0 0 a b ));
! DATA(insert (	26 3734    0 0 i b ));
! DATA(insert ( 3734	 26    0 0 i b ));
! DATA(insert (	20 3734 1287 0 i f ));
! DATA(insert (	21 3734  313 0 i f ));
! DATA(insert (	23 3734    0 0 i b ));
! DATA(insert ( 3734	 20 1288 0 a f ));
! DATA(insert ( 3734	 23    0 0 a b ));
! DATA(insert (	26 3769    0 0 i b ));
! DATA(insert ( 3769	 26    0 0 i b ));
! DATA(insert (	20 3769 1287 0 i f ));
! DATA(insert (	21 3769  313 0 i f ));
! DATA(insert (	23 3769    0 0 i b ));
! DATA(insert ( 3769	 20 1288 0 a f ));
! DATA(insert ( 3769	 23    0 0 a b ));
! DATA(insert (	25 2205 1079 0 i f ));
! DATA(insert ( 1043 2205 1079 0 i f ));
  
  /*
   * String category
   */
! DATA(insert (	25 1042    0 0 i b ));
! DATA(insert (	25 1043    0 0 i b ));
! DATA(insert ( 1042	 25  401 0 i f ));
! DATA(insert ( 1042 1043  401 0 i f ));
! DATA(insert ( 1043	 25    0 0 i b ));
! DATA(insert ( 1043 1042    0 0 i b ));
! DATA(insert (	18	 25  946 0 i f ));
! DATA(insert (	18 1042  860 0 a f ));
! DATA(insert (	18 1043  946 0 a f ));
! DATA(insert (	19	 25  406 0 i f ));
! DATA(insert (	19 1042  408 0 a f ));
! DATA(insert (	19 1043 1401 0 a f ));
! DATA(insert (	25	 18  944 0 a f ));
! DATA(insert ( 1042	 18  944 0 a f ));
! DATA(insert ( 1043	 18  944 0 a f ));
! DATA(insert (	25	 19  407 0 i f ));
! DATA(insert ( 1042	 19  409 0 i f ));
! DATA(insert ( 1043	 19 1400 0 i f ));
  
  /* Allow explicit coercions between int4 and "char" */
! DATA(insert (	18	 23   77 0 e f ));
! DATA(insert (	23	 18   78 0 e f ));
  
  /* pg_node_tree can be coerced to, but not from, text */
! DATA(insert (  194	 25    0 0 i b ));
  
  /*
   * Datetime category
   */
! DATA(insert (  702 1082 1179 0 a f ));
! DATA(insert (  702 1083 1364 0 a f ));
! DATA(insert (  702 1114 2023 0 i f ));
! DATA(insert (  702 1184 1173 0 i f ));
! DATA(insert (  703 1186 1177 0 i f ));
! DATA(insert ( 1082 1114 2024 0 i f ));
! DATA(insert ( 1082 1184 1174 0 i f ));
! DATA(insert ( 1083 1186 1370 0 i f ));
! DATA(insert ( 1083 1266 2047 0 i f ));
! DATA(insert ( 1114	702 2030 0 a f ));
! DATA(insert ( 1114 1082 2029 0 a f ));
! DATA(insert ( 1114 1083 1316 0 a f ));
! DATA(insert ( 1114 1184 2028 0 i f ));
! DATA(insert ( 1184	702 1180 0 a f ));
! DATA(insert ( 1184 1082 1178 0 a f ));
! DATA(insert ( 1184 1083 2019 0 a f ));
! DATA(insert ( 1184 1114 2027 0 a f ));
! DATA(insert ( 1184 1266 1388 0 a f ));
! DATA(insert ( 1186	703 1194 0 a f ));
! DATA(insert ( 1186 1083 1419 0 a f ));
! DATA(insert ( 1266 1083 2046 0 a f ));
  /* Cross-category casts between int4 and abstime, reltime */
! DATA(insert (	23	702    0 0 e b ));
! DATA(insert (  702	 23    0 0 e b ));
! DATA(insert (	23	703    0 0 e b ));
! DATA(insert (  703	 23    0 0 e b ));
  
  /*
   * Geometric category
   */
! DATA(insert (  601	600 1532 0 e f ));
! DATA(insert (  602	600 1533 0 e f ));
! DATA(insert (  602	604 1449 0 a f ));
! DATA(insert (  603	600 1534 0 e f ));
! DATA(insert (  603	601 1541 0 e f ));
! DATA(insert (  603	604 1448 0 a f ));
! DATA(insert (  603	718 1479 0 e f ));
! DATA(insert (  604	600 1540 0 e f ));
! DATA(insert (  604	602 1447 0 a f ));
! DATA(insert (  604	603 1446 0 e f ));
! DATA(insert (  604	718 1474 0 e f ));
! DATA(insert (  718	600 1416 0 e f ));
! DATA(insert (  718	603 1480 0 e f ));
! DATA(insert (  718	604 1544 0 e f ));
  
  /*
   * INET category
   */
! DATA(insert (  650	869    0 0 i b ));
! DATA(insert (  869	650 1715 0 a f ));
  
  /*
   * BitString category
   */
! DATA(insert ( 1560 1562    0 0 i b ));
! DATA(insert ( 1562 1560    0 0 i b ));
  /* Cross-category casts between bit and int4, int8 */
! DATA(insert (	20 1560 2075 0 e f ));
! DATA(insert (	23 1560 1683 0 e f ));
! DATA(insert ( 1560	 20 2076 0 e f ));
! DATA(insert ( 1560	 23 1684 0 e f ));
  
  /*
   * Cross-category casts to and from TEXT
***************
*** 315,360 **** DATA(insert ( 1560	 23 1684 e f ));
   * behavior will ensue when the automatic cast is applied instead of the
   * pg_cast entry!
   */
! DATA(insert (  650	 25  730 a f ));
! DATA(insert (  869	 25  730 a f ));
! DATA(insert (	16	 25 2971 a f ));
! DATA(insert (  142	 25    0 a b ));
! DATA(insert (	25	142 2896 e f ));
  
  /*
   * Cross-category casts to and from VARCHAR
   *
   * We support all the same casts as for TEXT.
   */
! DATA(insert (  650 1043  730 a f ));
! DATA(insert (  869 1043  730 a f ));
! DATA(insert (	16 1043 2971 a f ));
! DATA(insert (  142 1043    0 a b ));
! DATA(insert ( 1043	142 2896 e f ));
  
  /*
   * Cross-category casts to and from BPCHAR
   *
   * We support all the same casts as for TEXT.
   */
! DATA(insert (  650 1042  730 a f ));
! DATA(insert (  869 1042  730 a f ));
! DATA(insert (	16 1042 2971 a f ));
! DATA(insert (  142 1042    0 a b ));
! DATA(insert ( 1042	142 2896 e f ));
  
  /*
   * Length-coercion functions
   */
! DATA(insert ( 1042 1042  668 i f ));
! DATA(insert ( 1043 1043  669 i f ));
! DATA(insert ( 1083 1083 1968 i f ));
! DATA(insert ( 1114 1114 1961 i f ));
! DATA(insert ( 1184 1184 1967 i f ));
! DATA(insert ( 1186 1186 1200 i f ));
! DATA(insert ( 1266 1266 1969 i f ));
! DATA(insert ( 1560 1560 1685 i f ));
! DATA(insert ( 1562 1562 1687 i f ));
! DATA(insert ( 1700 1700 1703 i f ));
  
  #endif   /* PG_CAST_H */
--- 317,362 ----
   * behavior will ensue when the automatic cast is applied instead of the
   * pg_cast entry!
   */
! DATA(insert (  650	 25  730 0 a f ));
! DATA(insert (  869	 25  730 0 a f ));
! DATA(insert (	16	 25 2971 0 a f ));
! DATA(insert (  142	 25    0 0 a b ));
! DATA(insert (	25	142 2896 3831 e f ));
  
  /*
   * Cross-category casts to and from VARCHAR
   *
   * We support all the same casts as for TEXT.
   */
! DATA(insert (  650 1043  730 0 a f ));
! DATA(insert (  869 1043  730 0 a f ));
! DATA(insert (	16 1043 2971 0 a f ));
! DATA(insert (  142 1043    0 0 a b ));
! DATA(insert ( 1043	142 2896 3831 e f ));
  
  /*
   * Cross-category casts to and from BPCHAR
   *
   * We support all the same casts as for TEXT.
   */
! DATA(insert (  650 1042  730 0 a f ));
! DATA(insert (  869 1042  730 0 a f ));
! DATA(insert (	16 1042 2971 0 a f ));
! DATA(insert (  142 1042    0 0 a b ));
! DATA(insert ( 1042	142 2896 3831 e f ));
  
  /*
   * Length-coercion functions
   */
! DATA(insert ( 1042 1042  668 0 i f ));
! DATA(insert ( 1043 1043  669 3829 i f ));
! DATA(insert ( 1083 1083 1968 0 i f ));
! DATA(insert ( 1114 1114 1961 0 i f ));
! DATA(insert ( 1184 1184 1967 0 i f ));
! DATA(insert ( 1186 1186 1200 0 i f ));
! DATA(insert ( 1266 1266 1969 0 i f ));
! DATA(insert ( 1560 1560 1685 0 i f ));
! DATA(insert ( 1562 1562 1687 0 i f ));
! DATA(insert ( 1700 1700 1703 0 i f ));
  
  #endif   /* PG_CAST_H */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 937,942 **** DESCR("not equal");
--- 937,944 ----
  
  DATA(insert OID = 668 (  bpchar			   PGNSP PGUID 12 1 0 0 f f f t f i 3 0 1042 "1042 23 16" _null_ _null_ _null_ _null_ bpchar _null_ _null_ _null_ ));
  DESCR("adjust char() to typmod length");
+ DATA(insert OID = 3829 ( varchar_exemptor  PGNSP PGUID 12 1 0 0 f f f t f i 3 0 23 "23 23 16" _null_ _null_ _null_ _null_ varchar_exemptor _null_ _null_ _null_ ));
+ DESCR("varchar cast exemptor");
  DATA(insert OID = 669 (  varchar		   PGNSP PGUID 12 1 0 0 f f f t f i 3 0 1043 "1043 23 16" _null_ _null_ _null_ _null_ varchar _null_ _null_ _null_ ));
  DESCR("adjust varchar() to typmod length");
  
***************
*** 4437,4442 **** DATA(insert OID = 2894 (  xml_out		   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275
--- 4439,4446 ----
  DESCR("I/O");
  DATA(insert OID = 2895 (  xmlcomment	   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 142 "25" _null_ _null_ _null_ _null_ xmlcomment _null_ _null_ _null_ ));
  DESCR("generate an XML comment");
+ DATA(insert OID = 3831 (  xml_exemptor	   PGNSP PGUID 12 1 0 0 f f f t f i 3 0 23 "23 23 16" _null_ _null_ _null_ _null_ xml_exemptor _null_ _null_ _null_ ));
+ DESCR("texttoxml cast exemptor");
  DATA(insert OID = 2896 (  xml			   PGNSP PGUID 12 1 0 0 f f f t f s 1 0 142 "25" _null_ _null_ _null_ _null_ texttoxml _null_ _null_ _null_ ));
  DESCR("perform a non-validating parse of a character string to produce an XML value");
  DATA(insert OID = 2897 (  xmlvalidate	   PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "142 25" _null_ _null_ _null_ _null_ xmlvalidate _null_ _null_ _null_ ));
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2439,2444 **** typedef struct CreateCastStmt
--- 2439,2445 ----
  	TypeName   *sourcetype;
  	TypeName   *targettype;
  	FuncWithArgs *func;
+ 	FuncWithArgs *exemptor;
  	CoercionContext context;
  	bool		inout;
  } CreateCastStmt;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 313,318 **** typedef enum CoercionForm
--- 313,323 ----
  	COERCE_DONTCARE				/* special case for planner */
  } CoercionForm;
  
+ /* Bits in FuncExpr.funcexempt */
+ typedef int CoerceExemptions;
+ #define COERCE_EXEMPT_NOCHANGE	0x1		/* expression never changes storage */
+ #define COERCE_EXEMPT_NOERROR	0x2		/* expression never throws an error */
+ 
  /*
   * FuncExpr - expression node for a function call
   */
***************
*** 323,328 **** typedef struct FuncExpr
--- 328,334 ----
  	Oid			funcresulttype; /* PG_TYPE OID of result value */
  	bool		funcretset;		/* true if function returns set */
  	CoercionForm funcformat;	/* how to display this function call */
+ 	CoerceExemptions funcexempt;	/* potential optimizations */
  	List	   *args;			/* arguments to the function */
  	int			location;		/* token location, or -1 if unknown */
  } FuncExpr;
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 148,153 **** PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD)
--- 148,154 ----
  PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
  PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
  PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("exemptor", EXEMPTOR, UNRESERVED_KEYWORD)
  PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD)
  PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD)
  PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_coerce.h
--- b/src/include/parser/parse_coerce.h
***************
*** 30,40 **** typedef enum CoercionPathType
  	COERCION_PATH_COERCEVIAIO	/* need a CoerceViaIO node */
  } CoercionPathType;
  
- /* Bits in the return value of GetCoerceExemptions. */
- typedef int CoerceExemptions;
- 
- #define COERCE_EXEMPT_NOCHANGE	0x1		/* expression never changes storage */
- #define COERCE_EXEMPT_NOERROR	0x2		/* expression never throws an error */
  
  extern bool IsBinaryCoercible(Oid srctype, Oid targettype);
  extern CoerceExemptions GetCoerceExemptions(Node *expr,
--- 30,35 ----
***************
*** 90,97 **** extern Oid resolve_generic_type(Oid declared_type,
  extern CoercionPathType find_coercion_pathway(Oid targetTypeId,
  					  Oid sourceTypeId,
  					  CoercionContext ccontext,
! 					  Oid *funcid);
  extern CoercionPathType find_typmod_coercion_function(Oid typeId,
! 							  Oid *funcid);
  
  #endif   /* PARSE_COERCE_H */
--- 85,92 ----
  extern CoercionPathType find_coercion_pathway(Oid targetTypeId,
  					  Oid sourceTypeId,
  					  CoercionContext ccontext,
! 					  Oid *funcid, Oid *exemptid);
  extern CoercionPathType find_typmod_coercion_function(Oid typeId,
! 							  Oid *funcid, Oid *exemptid);
  
  #endif   /* PARSE_COERCE_H */
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 676,681 **** extern Datum varcharrecv(PG_FUNCTION_ARGS);
--- 676,682 ----
  extern Datum varcharsend(PG_FUNCTION_ARGS);
  extern Datum varchartypmodin(PG_FUNCTION_ARGS);
  extern Datum varchartypmodout(PG_FUNCTION_ARGS);
+ extern Datum varchar_exemptor(PG_FUNCTION_ARGS);
  extern Datum varchar(PG_FUNCTION_ARGS);
  
  /* varlena.c */
*** a/src/include/utils/xml.h
--- b/src/include/utils/xml.h
***************
*** 33,38 **** extern Datum xml_recv(PG_FUNCTION_ARGS);
--- 33,39 ----
  extern Datum xml_send(PG_FUNCTION_ARGS);
  extern Datum xmlcomment(PG_FUNCTION_ARGS);
  extern Datum xmlconcat2(PG_FUNCTION_ARGS);
+ extern Datum xml_exemptor(PG_FUNCTION_ARGS);
  extern Datum texttoxml(PG_FUNCTION_ARGS);
  extern Datum xmltotext(PG_FUNCTION_ARGS);
  extern Datum xmlvalidate(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
***************
*** 2046,2084 **** DEBUG:  Rewriting table "t"
  ERROR:  numeric field overflow
  DETAIL:  A field with precision 4, scale 3 must round to an absolute value less than 10^1.
  ALTER TABLE t ALTER string TYPE varchar(4);							-- noop
- DEBUG:  Rewriting table "t"
- DEBUG:  Rebuilding index "t_daytimetz_key"
- DEBUG:  Rebuilding index "t_daytime_key"
- DEBUG:  Rebuilding index "t_stamptz_key"
- DEBUG:  Rebuilding index "t_stamp_key"
- DEBUG:  Rebuilding index "t_timegap_key"
- DEBUG:  Rebuilding index "t_bits_key"
- DEBUG:  Rebuilding index "t_network_key"
- DEBUG:  Rebuilding index "t_strarr_idx"
- DEBUG:  Rebuilding index "t_square_idx"
- DEBUG:  Rebuilding index "t_touchy_f_idx"
- DEBUG:  Rebuilding index "t_expr_idx"
- DEBUG:  Rebuilding index "t_constraint4_key"
- DEBUG:  Rebuilding index "t_integral_key"
- DEBUG:  Rebuilding index "t_rational_key"
  DEBUG:  Rebuilding index "t_string_idx1"
  DEBUG:  Rebuilding index "t_string_idx"
  ALTER TABLE t ALTER string TYPE lendom;								-- noop
- DEBUG:  Rewriting table "t"
- DEBUG:  Rebuilding index "t_daytimetz_key"
- DEBUG:  Rebuilding index "t_daytime_key"
- DEBUG:  Rebuilding index "t_stamptz_key"
- DEBUG:  Rebuilding index "t_stamp_key"
- DEBUG:  Rebuilding index "t_timegap_key"
- DEBUG:  Rebuilding index "t_bits_key"
- DEBUG:  Rebuilding index "t_network_key"
- DEBUG:  Rebuilding index "t_strarr_idx"
- DEBUG:  Rebuilding index "t_square_idx"
- DEBUG:  Rebuilding index "t_touchy_f_idx"
- DEBUG:  Rebuilding index "t_expr_idx"
- DEBUG:  Rebuilding index "t_constraint4_key"
- DEBUG:  Rebuilding index "t_integral_key"
- DEBUG:  Rebuilding index "t_rational_key"
  DEBUG:  Rebuilding index "t_string_idx"
  DEBUG:  Rebuilding index "t_string_idx1"
  ALTER TABLE t ALTER string TYPE shortdom;							-- rewrite-e
--- 2046,2054 ----
***************
*** 2732,2754 **** DEBUG:  Rebuilding index "t_bits_key"
  DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER document TYPE text;								-- noop
  ALTER TABLE t ALTER document TYPE xml USING document::xml;			-- verify
! DEBUG:  Rewriting table "t"
! DEBUG:  Rebuilding index "t_strarr_idx"
! DEBUG:  Rebuilding index "t_square_idx"
! DEBUG:  Rebuilding index "t_touchy_f_idx"
! DEBUG:  Rebuilding index "t_expr_idx"
! DEBUG:  Rebuilding index "t_constraint4_key"
! DEBUG:  Rebuilding index "t_integral_key"
! DEBUG:  Rebuilding index "t_rational_key"
! DEBUG:  Rebuilding index "t_string_idx1"
! DEBUG:  Rebuilding index "t_string_idx"
! DEBUG:  Rebuilding index "t_daytimetz_key"
! DEBUG:  Rebuilding index "t_daytime_key"
! DEBUG:  Rebuilding index "t_stamptz_key"
! DEBUG:  Rebuilding index "t_stamp_key"
! DEBUG:  Rebuilding index "t_timegap_key"
! DEBUG:  Rebuilding index "t_bits_key"
! DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER document TYPE char(20);							-- rewrite
  DEBUG:  Rewriting table "t"
  DEBUG:  Rebuilding index "t_strarr_idx"
--- 2702,2708 ----
  DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER document TYPE text;								-- noop
  ALTER TABLE t ALTER document TYPE xml USING document::xml;			-- verify
! DEBUG:  Verifying table "t"
  ALTER TABLE t ALTER document TYPE char(20);							-- rewrite
  DEBUG:  Rewriting table "t"
  DEBUG:  Rebuilding index "t_strarr_idx"
***************
*** 2768,2790 **** DEBUG:  Rebuilding index "t_timegap_key"
  DEBUG:  Rebuilding index "t_bits_key"
  DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER document TYPE xml USING document::xml;			-- verify
! DEBUG:  Rewriting table "t"
! DEBUG:  Rebuilding index "t_strarr_idx"
! DEBUG:  Rebuilding index "t_square_idx"
! DEBUG:  Rebuilding index "t_touchy_f_idx"
! DEBUG:  Rebuilding index "t_expr_idx"
! DEBUG:  Rebuilding index "t_constraint4_key"
! DEBUG:  Rebuilding index "t_integral_key"
! DEBUG:  Rebuilding index "t_rational_key"
! DEBUG:  Rebuilding index "t_string_idx1"
! DEBUG:  Rebuilding index "t_string_idx"
! DEBUG:  Rebuilding index "t_daytimetz_key"
! DEBUG:  Rebuilding index "t_daytime_key"
! DEBUG:  Rebuilding index "t_stamptz_key"
! DEBUG:  Rebuilding index "t_stamp_key"
! DEBUG:  Rebuilding index "t_timegap_key"
! DEBUG:  Rebuilding index "t_bits_key"
! DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER document TYPE varchar(30);						-- rewrite-v
  DEBUG:  Rewriting table "t"
  DEBUG:  Rebuilding index "t_strarr_idx"
--- 2722,2728 ----
  DEBUG:  Rebuilding index "t_bits_key"
  DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER document TYPE xml USING document::xml;			-- verify
! DEBUG:  Verifying table "t"
  ALTER TABLE t ALTER document TYPE varchar(30);						-- rewrite-v
  DEBUG:  Rewriting table "t"
  DEBUG:  Rebuilding index "t_strarr_idx"
***************
*** 2804,2843 **** DEBUG:  Rebuilding index "t_timegap_key"
  DEBUG:  Rebuilding index "t_bits_key"
  DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER document TYPE xml USING document::xml;			-- verify
! DEBUG:  Rewriting table "t"
! DEBUG:  Rebuilding index "t_strarr_idx"
! DEBUG:  Rebuilding index "t_square_idx"
! DEBUG:  Rebuilding index "t_touchy_f_idx"
! DEBUG:  Rebuilding index "t_expr_idx"
! DEBUG:  Rebuilding index "t_constraint4_key"
! DEBUG:  Rebuilding index "t_integral_key"
! DEBUG:  Rebuilding index "t_rational_key"
! DEBUG:  Rebuilding index "t_string_idx1"
! DEBUG:  Rebuilding index "t_string_idx"
! DEBUG:  Rebuilding index "t_daytimetz_key"
! DEBUG:  Rebuilding index "t_daytime_key"
! DEBUG:  Rebuilding index "t_stamptz_key"
! DEBUG:  Rebuilding index "t_stamp_key"
! DEBUG:  Rebuilding index "t_timegap_key"
! DEBUG:  Rebuilding index "t_bits_key"
! DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER strarr TYPE varchar(4)[];						-- noop
- DEBUG:  Rewriting table "t"
- DEBUG:  Rebuilding index "t_square_idx"
- DEBUG:  Rebuilding index "t_touchy_f_idx"
- DEBUG:  Rebuilding index "t_expr_idx"
- DEBUG:  Rebuilding index "t_constraint4_key"
- DEBUG:  Rebuilding index "t_integral_key"
- DEBUG:  Rebuilding index "t_rational_key"
- DEBUG:  Rebuilding index "t_string_idx1"
- DEBUG:  Rebuilding index "t_string_idx"
- DEBUG:  Rebuilding index "t_daytimetz_key"
- DEBUG:  Rebuilding index "t_daytime_key"
- DEBUG:  Rebuilding index "t_stamptz_key"
- DEBUG:  Rebuilding index "t_stamp_key"
- DEBUG:  Rebuilding index "t_timegap_key"
- DEBUG:  Rebuilding index "t_bits_key"
- DEBUG:  Rebuilding index "t_network_key"
  DEBUG:  Rebuilding index "t_strarr_idx"
  ALTER TABLE t ALTER strarr TYPE varchar(3)[];						-- rewrite-v
  DEBUG:  Rewriting table "t"
--- 2742,2749 ----
  DEBUG:  Rebuilding index "t_bits_key"
  DEBUG:  Rebuilding index "t_network_key"
  ALTER TABLE t ALTER document TYPE xml USING document::xml;			-- verify
! DEBUG:  Verifying table "t"
  ALTER TABLE t ALTER strarr TYPE varchar(4)[];						-- noop
  DEBUG:  Rebuilding index "t_strarr_idx"
  ALTER TABLE t ALTER strarr TYPE varchar(3)[];						-- rewrite-v
  DEBUG:  Rewriting table "t"
*** a/src/test/regress/expected/create_cast.out
--- b/src/test/regress/expected/create_cast.out
***************
*** 17,22 **** CREATE TYPE casttesttype (
--- 17,24 ----
     internallength = variable,
     input = casttesttype_in,
     output = casttesttype_out,
+    typmod_in = varchartypmodin,
+    typmod_out = varchartypmodout,
     alignment = int4
  );
  -- a dummy function to test with
***************
*** 49,54 **** SELECT casttestfunc('foo'::text); -- Should work now
--- 51,57 ----
              1
  (1 row)
  
+ DROP CAST (text AS casttesttype); -- cleanup
  -- Try I/O conversion cast.
  SELECT 1234::int4::casttesttype; -- No cast yet, should fail
  ERROR:  cannot cast type integer to casttesttype
***************
*** 72,74 **** SELECT 1234::int4::casttesttype; -- Should work now
--- 75,110 ----
   foo1234
  (1 row)
  
+ -- Try an EXEMPTOR to skip work during ALTER TABLE.  Make casttesttype behave
+ -- like varchar, but without the special trailing blank treatment during an
+ -- implicit cast.
+ SET client_min_messages = debug1;
+ CREATE CAST (text AS casttesttype) WITHOUT FUNCTION;
+ CREATE FUNCTION casttest_lencoerce(casttesttype, int, boolean)
+ RETURNS casttesttype LANGUAGE SQL IMMUTABLE STRICT AS $$
+ SELECT CASE
+ 	WHEN length($1::text) <= $2 THEN	$1
+ 	WHEN $3 THEN						substring($1::text from 1 for $2)::casttesttype
+ 	ELSE ('f' || $1)::numeric::text::casttesttype -- throws an error
+ END
+ $$;
+ CREATE FUNCTION casttest_exemptor(oldtypmod int, newtypmod int, explicit bool)
+ RETURNS int LANGUAGE SQL IMMUTABLE STRICT AS $$
+ SELECT CASE
+ 	WHEN $1 <= $2 THEN	3		-- no change, no error
+ 	WHEN $3 THEN		0		-- explicit shrink: can truncate
+ 	ELSE				1		-- implciit shrink: no change, might error
+ END
+ $$;
+ CREATE CAST (casttesttype AS casttesttype)
+ 	WITH FUNCTION casttest_lencoerce(casttesttype, int, boolean)
+ 	EXEMPTOR casttest_exemptor(int, int, boolean);
+ CREATE TABLE casttbl (c casttesttype(10));
+ INSERT INTO casttbl VALUES ('foo'), ('bar');
+ ALTER TABLE casttbl ALTER c TYPE casttesttype(11);							 -- noop
+ ALTER TABLE casttbl ALTER c TYPE casttesttype(10);							 -- verify
+ DEBUG:  Verifying table "casttbl"
+ ALTER TABLE casttbl ALTER c TYPE casttesttype(11) USING c::casttesttype(11); -- noop
+ ALTER TABLE casttbl ALTER c TYPE casttesttype(10) USING c::casttesttype(10); -- rewrite
+ DEBUG:  Rewriting table "casttbl"
+ RESET client_min_messages;
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 331,338 **** SELECT *
  FROM pg_cast c
  WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
      OR castmethod NOT IN ('f', 'b' ,'i');
!  castsource | casttarget | castfunc | castcontext | castmethod 
! ------------+------------+----------+-------------+------------
  (0 rows)
  
  -- Check that castfunc is nonzero only for cast methods that need a function,
--- 331,338 ----
  FROM pg_cast c
  WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
      OR castmethod NOT IN ('f', 'b' ,'i');
!  castsource | casttarget | castfunc | castexemptor | castcontext | castmethod 
! ------------+------------+----------+--------------+-------------+------------
  (0 rows)
  
  -- Check that castfunc is nonzero only for cast methods that need a function,
***************
*** 341,348 **** SELECT *
  FROM pg_cast c
  WHERE (castmethod = 'f' AND castfunc = 0)
     OR (castmethod IN ('b', 'i') AND castfunc <> 0);
!  castsource | casttarget | castfunc | castcontext | castmethod 
! ------------+------------+----------+-------------+------------
  (0 rows)
  
  -- Look for casts to/from the same type that aren't length coercion functions.
--- 341,348 ----
  FROM pg_cast c
  WHERE (castmethod = 'f' AND castfunc = 0)
     OR (castmethod IN ('b', 'i') AND castfunc <> 0);
!  castsource | casttarget | castfunc | castexemptor | castcontext | castmethod 
! ------------+------------+----------+--------------+-------------+------------
  (0 rows)
  
  -- Look for casts to/from the same type that aren't length coercion functions.
***************
*** 351,365 **** WHERE (castmethod = 'f' AND castfunc = 0)
  SELECT *
  FROM pg_cast c
  WHERE castsource = casttarget AND castfunc = 0;
!  castsource | casttarget | castfunc | castcontext | castmethod 
! ------------+------------+----------+-------------+------------
  (0 rows)
  
  SELECT c.*
  FROM pg_cast c, pg_proc p
  WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
!  castsource | casttarget | castfunc | castcontext | castmethod 
! ------------+------------+----------+-------------+------------
  (0 rows)
  
  -- Look for cast functions that don't have the right signature.  The
--- 351,365 ----
  SELECT *
  FROM pg_cast c
  WHERE castsource = casttarget AND castfunc = 0;
!  castsource | casttarget | castfunc | castexemptor | castcontext | castmethod 
! ------------+------------+----------+--------------+-------------+------------
  (0 rows)
  
  SELECT c.*
  FROM pg_cast c, pg_proc p
  WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
!  castsource | casttarget | castfunc | castexemptor | castcontext | castmethod 
! ------------+------------+----------+--------------+-------------+------------
  (0 rows)
  
  -- Look for cast functions that don't have the right signature.  The
***************
*** 377,384 **** WHERE c.castfunc = p.oid AND
               OR (c.castsource = 'character'::regtype AND
                   p.proargtypes[0] = 'text'::regtype))
       OR NOT binary_coercible(p.prorettype, c.casttarget));
!  castsource | casttarget | castfunc | castcontext | castmethod 
! ------------+------------+----------+-------------+------------
  (0 rows)
  
  SELECT c.*
--- 377,384 ----
               OR (c.castsource = 'character'::regtype AND
                   p.proargtypes[0] = 'text'::regtype))
       OR NOT binary_coercible(p.prorettype, c.casttarget));
!  castsource | casttarget | castfunc | castexemptor | castcontext | castmethod 
! ------------+------------+----------+--------------+-------------+------------
  (0 rows)
  
  SELECT c.*
***************
*** 386,393 **** FROM pg_cast c, pg_proc p
  WHERE c.castfunc = p.oid AND
      ((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
       (p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
!  castsource | casttarget | castfunc | castcontext | castmethod 
! ------------+------------+----------+-------------+------------
  (0 rows)
  
  -- Look for binary compatible casts that do not have the reverse
--- 386,393 ----
  WHERE c.castfunc = p.oid AND
      ((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
       (p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
!  castsource | casttarget | castfunc | castexemptor | castcontext | castmethod 
! ------------+------------+----------+--------------+-------------+------------
  (0 rows)
  
  -- Look for binary compatible casts that do not have the reverse
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
***************
*** 27,32 **** SELECT relname, relhasindex
--- 27,33 ----
   bt_txt_heap             | t
   c                       | f
   c_star                  | f
+  casttbl                 | f
   char_tbl                | f
   check2_tbl              | f
   check_tbl               | f
***************
*** 155,161 **** SELECT relname, relhasindex
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (144 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
--- 156,162 ----
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (145 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
*** a/src/test/regress/output/misc.source
--- b/src/test/regress/output/misc.source
***************
*** 585,590 **** SELECT user_relns() AS user_relns
--- 585,591 ----
   bt_txt_heap
   c
   c_star
+  casttbl
   char_tbl
   check2_tbl
   check_seq
***************
*** 669,675 **** SELECT user_relns() AS user_relns
   toyemp
   varchar_tbl
   xacttest
! (102 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
--- 670,676 ----
   toyemp
   varchar_tbl
   xacttest
! (103 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
*** a/src/test/regress/sql/create_cast.sql
--- b/src/test/regress/sql/create_cast.sql
***************
*** 18,23 **** CREATE TYPE casttesttype (
--- 18,25 ----
     internallength = variable,
     input = casttesttype_in,
     output = casttesttype_out,
+    typmod_in = varchartypmodin,
+    typmod_out = varchartypmodout,
     alignment = int4
  );
  
***************
*** 36,41 **** DROP CAST (text AS casttesttype); -- cleanup
--- 38,44 ----
  -- Try IMPLICIT binary coercion cast
  CREATE CAST (text AS casttesttype) WITHOUT FUNCTION AS IMPLICIT;
  SELECT casttestfunc('foo'::text); -- Should work now
+ DROP CAST (text AS casttesttype); -- cleanup
  
  -- Try I/O conversion cast.
  SELECT 1234::int4::casttesttype; -- No cast yet, should fail
***************
*** 52,54 **** $$ SELECT ('foo'::text || $1::text)::casttesttype; $$;
--- 55,94 ----
  
  CREATE CAST (int4 AS casttesttype) WITH FUNCTION int4_casttesttype(int4) AS IMPLICIT;
  SELECT 1234::int4::casttesttype; -- Should work now
+ 
+ -- Try an EXEMPTOR to skip work during ALTER TABLE.  Make casttesttype behave
+ -- like varchar, but without the special trailing blank treatment during an
+ -- implicit cast.
+ SET client_min_messages = debug1;
+ CREATE CAST (text AS casttesttype) WITHOUT FUNCTION;
+ 
+ CREATE FUNCTION casttest_lencoerce(casttesttype, int, boolean)
+ RETURNS casttesttype LANGUAGE SQL IMMUTABLE STRICT AS $$
+ SELECT CASE
+ 	WHEN length($1::text) <= $2 THEN	$1
+ 	WHEN $3 THEN						substring($1::text from 1 for $2)::casttesttype
+ 	ELSE ('f' || $1)::numeric::text::casttesttype -- throws an error
+ END
+ $$;
+ 
+ CREATE FUNCTION casttest_exemptor(oldtypmod int, newtypmod int, explicit bool)
+ RETURNS int LANGUAGE SQL IMMUTABLE STRICT AS $$
+ SELECT CASE
+ 	WHEN $1 <= $2 THEN	3		-- no change, no error
+ 	WHEN $3 THEN		0		-- explicit shrink: can truncate
+ 	ELSE				1		-- implciit shrink: no change, might error
+ END
+ $$;
+ 
+ CREATE CAST (casttesttype AS casttesttype)
+ 	WITH FUNCTION casttest_lencoerce(casttesttype, int, boolean)
+ 	EXEMPTOR casttest_exemptor(int, int, boolean);
+ 
+ CREATE TABLE casttbl (c casttesttype(10));
+ INSERT INTO casttbl VALUES ('foo'), ('bar');
+ ALTER TABLE casttbl ALTER c TYPE casttesttype(11);							 -- noop
+ ALTER TABLE casttbl ALTER c TYPE casttesttype(10);							 -- verify
+ ALTER TABLE casttbl ALTER c TYPE casttesttype(11) USING c::casttesttype(11); -- noop
+ ALTER TABLE casttbl ALTER c TYPE casttesttype(10) USING c::casttesttype(10); -- rewrite
+ 
+ RESET client_min_messages;