#include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
+#include "parser/parse_coerce.h"
 #include "parser/scansup.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
    bool       *freevals;       /* which arguments are pfree-able */
 } PreparedParamsData;
 
+typedef struct
+{
+   /* NB: we assume this struct contains no padding bytes */
+   Oid         srctype;        /* source type for cast */
+   Oid         dsttype;        /* destination type for cast */
+   int32       dsttypmod;      /* destination typmod for cast */
+} plpgsql_CastHashKey;
+
+typedef struct
+{
+   plpgsql_CastHashKey key;    /* hash key --- MUST BE FIRST */
+   ExprState  *cast_exprstate; /* cast expression, or NULL if no-op cast */
+} plpgsql_CastHashEntry;
+
 /*
  * All plpgsql function executions within a single transaction share the same
  * executor EState for evaluating "simple" expressions.  Each function call
 static char *convert_value_to_string(PLpgSQL_execstate *estate,
                        Datum value, Oid valtype);
 static Datum exec_cast_value(PLpgSQL_execstate *estate,
-               Datum value, bool isnull,
+               Datum value, bool *isnull,
                Oid valtype, int32 valtypmod,
-               Oid reqtype, int32 reqtypmod,
-               FmgrInfo *reqinput,
-               Oid reqtypioparam);
-static Datum exec_simple_cast_value(PLpgSQL_execstate *estate,
-                      Datum value, bool isnull,
-                      Oid valtype, int32 valtypmod,
-                      Oid reqtype, int32 reqtypmod);
+               Oid reqtype, int32 reqtypmod);
+static ExprState *get_cast_expression(PLpgSQL_execstate *estate,
+                   Oid srctype, Oid dsttype, int32 dsttypmod);
 static void exec_init_tuple_store(PLpgSQL_execstate *estate);
 static void exec_set_found(PLpgSQL_execstate *estate, bool state);
 static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
            /* Cast value to proper type */
            estate.retval = exec_cast_value(&estate,
                                            estate.retval,
-                                           fcinfo->isnull,
+                                           &fcinfo->isnull,
                                            estate.rettype,
                                            -1,
                                            func->fn_rettype,
-                                           -1,
-                                           &(func->fn_retinput),
-                                           func->fn_rettypioparam);
+                                           -1);
 
            /*
             * If the function's return type isn't by value, copy the value
                         * before the notnull check to be consistent with
                         * exec_assign_value.)
                         */
-                       if (!var->datatype->typinput.fn_strict)
+                       if (var->datatype->typtype == TYPTYPE_DOMAIN)
                            exec_assign_value(estate,
                                              (PLpgSQL_datum *) var,
                                              (Datum) 0,
     */
    value = exec_eval_expr(estate, stmt->lower,
                           &isnull, &valtype, &valtypmod);
-   value = exec_cast_value(estate, value, isnull,
+   value = exec_cast_value(estate, value, &isnull,
                            valtype, valtypmod,
                            var->datatype->typoid,
-                           var->datatype->atttypmod,
-                           &(var->datatype->typinput),
-                           var->datatype->typioparam);
+                           var->datatype->atttypmod);
    if (isnull)
        ereport(ERROR,
                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     */
    value = exec_eval_expr(estate, stmt->upper,
                           &isnull, &valtype, &valtypmod);
-   value = exec_cast_value(estate, value, isnull,
+   value = exec_cast_value(estate, value, &isnull,
                            valtype, valtypmod,
                            var->datatype->typoid,
-                           var->datatype->atttypmod,
-                           &(var->datatype->typinput),
-                           var->datatype->typioparam);
+                           var->datatype->atttypmod);
    if (isnull)
        ereport(ERROR,
                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
    {
        value = exec_eval_expr(estate, stmt->step,
                               &isnull, &valtype, &valtypmod);
-       value = exec_cast_value(estate, value, isnull,
+       value = exec_cast_value(estate, value, &isnull,
                                valtype, valtypmod,
                                var->datatype->typoid,
-                               var->datatype->atttypmod,
-                               &(var->datatype->typinput),
-                               var->datatype->typioparam);
+                               var->datatype->atttypmod);
        if (isnull)
            ereport(ERROR,
                    (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                        errmsg("wrong result type supplied in RETURN NEXT")));
 
                    /* coerce type if needed */
-                   retval = exec_simple_cast_value(estate,
-                                                   retval,
-                                                   isNull,
-                                                   var->datatype->typoid,
-                                                   var->datatype->atttypmod,
-                                                tupdesc->attrs[0]->atttypid,
-                                              tupdesc->attrs[0]->atttypmod);
+                   retval = exec_cast_value(estate,
+                                            retval,
+                                            &isNull,
+                                            var->datatype->typoid,
+                                            var->datatype->atttypmod,
+                                            tupdesc->attrs[0]->atttypid,
+                                            tupdesc->attrs[0]->atttypmod);
 
                    tuplestore_putvalues(estate->tuple_store, tupdesc,
                                         &retval, &isNull);
                       errmsg("wrong result type supplied in RETURN NEXT")));
 
            /* coerce type if needed */
-           retval = exec_simple_cast_value(estate,
-                                           retval,
-                                           isNull,
-                                           rettype,
-                                           rettypmod,
-                                           tupdesc->attrs[0]->atttypid,
-                                           tupdesc->attrs[0]->atttypmod);
+           retval = exec_cast_value(estate,
+                                    retval,
+                                    &isNull,
+                                    rettype,
+                                    rettypmod,
+                                    tupdesc->attrs[0]->atttypid,
+                                    tupdesc->attrs[0]->atttypmod);
 
            tuplestore_putvalues(estate->tuple_store, tupdesc,
                                 &retval, &isNull);
 
                newvalue = exec_cast_value(estate,
                                           value,
-                                          isNull,
+                                          &isNull,
                                           valtype,
                                           valtypmod,
                                           var->datatype->typoid,
-                                          var->datatype->atttypmod,
-                                          &(var->datatype->typinput),
-                                          var->datatype->typioparam);
+                                          var->datatype->atttypmod);
 
                if (isNull && var->notnull)
                    ereport(ERROR,
                 */
                atttype = rec->tupdesc->attrs[fno]->atttypid;
                atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
-               values[fno] = exec_simple_cast_value(estate,
-                                                    value,
-                                                    isNull,
-                                                    valtype,
-                                                    valtypmod,
-                                                    atttype,
-                                                    atttypmod);
+               values[fno] = exec_cast_value(estate,
+                                             value,
+                                             &isNull,
+                                             valtype,
+                                             valtypmod,
+                                             atttype,
+                                             atttypmod);
                nulls[fno] = isNull;
 
                /*
                estate->eval_tuptable = save_eval_tuptable;
 
                /* Coerce source value to match array element type. */
-               coerced_value = exec_simple_cast_value(estate,
-                                                      value,
-                                                      isNull,
-                                                      valtype,
-                                                      valtypmod,
-                                                      arrayelem->elemtypoid,
-                                                    arrayelem->arraytypmod);
+               coerced_value = exec_cast_value(estate,
+                                               value,
+                                               &isNull,
+                                               valtype,
+                                               valtypmod,
+                                               arrayelem->elemtypoid,
+                                               arrayelem->arraytypmod);
 
                /*
                 * If the original array is null, cons up an empty array so
    int32       exprtypmod;
 
    exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
-   exprdatum = exec_simple_cast_value(estate, exprdatum, *isNull,
-                                      exprtypeid, exprtypmod,
-                                      INT4OID, -1);
+   exprdatum = exec_cast_value(estate, exprdatum, isNull,
+                               exprtypeid, exprtypmod,
+                               INT4OID, -1);
    return DatumGetInt32(exprdatum);
 }
 
    int32       exprtypmod;
 
    exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
-   exprdatum = exec_simple_cast_value(estate, exprdatum, *isNull,
-                                      exprtypeid, exprtypmod,
-                                      BOOLOID, -1);
+   exprdatum = exec_cast_value(estate, exprdatum, isNull,
+                               exprtypeid, exprtypmod,
+                               BOOLOID, -1);
    return DatumGetBool(exprdatum);
 }
 
  * pass-by-reference) and so an exec_eval_cleanup() call is needed anyway.
  *
  * Note: not caching the conversion function lookup is bad for performance.
+ * However, this function isn't currently used in any places where an extra
+ * catalog lookup or two seems like a big deal.
  * ----------
  */
 static char *
 /* ----------
  * exec_cast_value         Cast a value if required
  *
+ * Note that *isnull is an input and also an output parameter.  While it's
+ * unlikely that a cast operation would produce null from non-null or vice
+ * versa, that could happen in principle.
+ *
  * Note: the estate's eval_econtext is used for temporary storage, and may
  * also contain the result Datum if we have to do a conversion to a pass-
  * by-reference data type.  Be sure to do an exec_eval_cleanup() call when
  */
 static Datum
 exec_cast_value(PLpgSQL_execstate *estate,
-               Datum value, bool isnull,
+               Datum value, bool *isnull,
                Oid valtype, int32 valtypmod,
-               Oid reqtype, int32 reqtypmod,
-               FmgrInfo *reqinput,
-               Oid reqtypioparam)
+               Oid reqtype, int32 reqtypmod)
 {
    /*
     * If the type of the given value isn't what's requested, convert it.
    if (valtype != reqtype ||
        (valtypmod != reqtypmod && reqtypmod != -1))
    {
-       MemoryContext oldcontext;
+       ExprState  *cast_expr;
 
-       oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
-       if (!isnull)
+       cast_expr = get_cast_expression(estate, valtype, reqtype, reqtypmod);
+       if (cast_expr)
        {
-           char       *extval;
+           ExprContext *econtext = estate->eval_econtext;
+           MemoryContext oldcontext;
 
-           extval = convert_value_to_string(estate, value, valtype);
-           value = InputFunctionCall(reqinput, extval,
-                                     reqtypioparam, reqtypmod);
-       }
-       else
-       {
-           value = InputFunctionCall(reqinput, NULL,
-                                     reqtypioparam, reqtypmod);
+           oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+           econtext->caseValue_datum = value;
+           econtext->caseValue_isNull = *isnull;
+
+           value = ExecEvalExpr(cast_expr, econtext, isnull, NULL);
+
+           MemoryContextSwitchTo(oldcontext);
        }
-       MemoryContextSwitchTo(oldcontext);
    }
 
    return value;
 }
 
 /* ----------
- * exec_simple_cast_value          Cast a value if required
+ * get_cast_expression         Look up how to perform a type cast
  *
- * As above, but need not supply details about target type.  Note that this
- * is slower than exec_cast_value with cached type info, and so should be
- * avoided in heavily used code paths.
+ * Returns an expression evaluation tree based on a CaseTestExpr input,
+ * or NULL if the cast is a mere no-op relabeling.
+ *
+ * We cache the results of the lookup in a per-function hash table.
+ * It's tempting to consider using a session-wide hash table instead,
+ * but that introduces some corner-case questions that probably aren't
+ * worth dealing with; in particular that re-entrant use of an evaluation
+ * tree might occur.  That would also set in stone the assumption that
+ * collation isn't important to a cast function.
  * ----------
  */
-static Datum
-exec_simple_cast_value(PLpgSQL_execstate *estate,
-                      Datum value, bool isnull,
-                      Oid valtype, int32 valtypmod,
-                      Oid reqtype, int32 reqtypmod)
+static ExprState *
+get_cast_expression(PLpgSQL_execstate *estate,
+                   Oid srctype, Oid dsttype, int32 dsttypmod)
 {
-   if (valtype != reqtype ||
-       (valtypmod != reqtypmod && reqtypmod != -1))
+   HTAB       *cast_hash = estate->func->cast_hash;
+   plpgsql_CastHashKey cast_key;
+   plpgsql_CastHashEntry *cast_entry;
+   bool        found;
+   CaseTestExpr *placeholder;
+   Node       *cast_expr;
+   ExprState  *cast_exprstate;
+   MemoryContext oldcontext;
+
+   /* Create the cast-info hash table if we didn't already */
+   if (cast_hash == NULL)
    {
-       Oid         typinput;
-       Oid         typioparam;
-       FmgrInfo    finfo_input;
-
-       getTypeInputInfo(reqtype, &typinput, &typioparam);
-
-       fmgr_info(typinput, &finfo_input);
-
-       value = exec_cast_value(estate,
-                               value,
-                               isnull,
-                               valtype,
-                               valtypmod,
-                               reqtype,
-                               reqtypmod,
-                               &finfo_input,
-                               typioparam);
+       HASHCTL     ctl;
+
+       memset(&ctl, 0, sizeof(ctl));
+       ctl.keysize = sizeof(plpgsql_CastHashKey);
+       ctl.entrysize = sizeof(plpgsql_CastHashEntry);
+       ctl.hcxt = estate->func->fn_cxt;
+       cast_hash = hash_create("PLpgSQL cast cache",
+                               16,     /* start small and extend */
+                               &ctl,
+                               HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+       estate->func->cast_hash = cast_hash;
    }
 
-   return value;
-}
+   /* Look for existing entry */
+   cast_key.srctype = srctype;
+   cast_key.dsttype = dsttype;
+   cast_key.dsttypmod = dsttypmod;
+   cast_entry = (plpgsql_CastHashEntry *) hash_search(cast_hash,
+                                                      (void *) &cast_key,
+                                                      HASH_FIND, NULL);
+   if (cast_entry)
+       return cast_entry->cast_exprstate;
+
+   /* Construct expression tree for coercion in function's context */
+   oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt);
 
+   /*
+    * We use a CaseTestExpr as the base of the coercion tree, since it's very
+    * cheap to insert the source value for that.
+    */
+   placeholder = makeNode(CaseTestExpr);
+   placeholder->typeId = srctype;
+   placeholder->typeMod = -1;
+   placeholder->collation = get_typcollation(srctype);
+   if (OidIsValid(estate->func->fn_input_collation) &&
+       OidIsValid(placeholder->collation))
+       placeholder->collation = estate->func->fn_input_collation;
+
+   /*
+    * Apply coercion.  We use ASSIGNMENT coercion because that's the closest
+    * match to plpgsql's historical behavior; in particular, EXPLICIT
+    * coercion would allow silent truncation to a destination
+    * varchar/bpchar's length, which we do not want.
+    *
+    * If source type is UNKNOWN, coerce_to_target_type will fail (it only
+    * expects to see that for Const input nodes), so don't call it; we'll
+    * apply CoerceViaIO instead.
+    */
+   if (srctype != UNKNOWNOID)
+       cast_expr = coerce_to_target_type(NULL,
+                                         (Node *) placeholder, srctype,
+                                         dsttype, dsttypmod,
+                                         COERCION_ASSIGNMENT,
+                                         COERCE_IMPLICIT_CAST,
+                                         -1);
+   else
+       cast_expr = NULL;
+
+   /*
+    * If there's no cast path according to the parser, fall back to using an
+    * I/O coercion; this is semantically dubious but matches plpgsql's
+    * historical behavior.  We would need something of the sort for UNKNOWN
+    * literals in any case.
+    */
+   if (cast_expr == NULL)
+   {
+       CoerceViaIO *iocoerce = makeNode(CoerceViaIO);
+
+       iocoerce->arg = (Expr *) placeholder;
+       iocoerce->resulttype = dsttype;
+       iocoerce->resultcollid = InvalidOid;
+       iocoerce->coerceformat = COERCE_IMPLICIT_CAST;
+       iocoerce->location = -1;
+       cast_expr = (Node *) iocoerce;
+       if (dsttypmod != -1)
+           cast_expr = coerce_to_target_type(NULL,
+                                             cast_expr, dsttype,
+                                             dsttype, dsttypmod,
+                                             COERCION_ASSIGNMENT,
+                                             COERCE_IMPLICIT_CAST,
+                                             -1);
+   }
+
+   /* Note: we don't bother labeling the expression tree with collation */
+
+   /* Detect whether we have a no-op (RelabelType) coercion */
+   if (IsA(cast_expr, RelabelType) &&
+       ((RelabelType *) cast_expr)->arg == (Expr *) placeholder)
+       cast_expr = NULL;
+
+   if (cast_expr)
+   {
+       /* ExecInitExpr assumes we've planned the expression */
+       cast_expr = (Node *) expression_planner((Expr *) cast_expr);
+       /* Create an expression eval state tree for it */
+       cast_exprstate = ExecInitExpr((Expr *) cast_expr, NULL);
+   }
+   else
+       cast_exprstate = NULL;
+
+   MemoryContextSwitchTo(oldcontext);
+
+   /*
+    * Now fill in a hashtable entry.  If we fail anywhere up to/including
+    * this step, we've only leaked some memory in the function context, which
+    * isn't great but isn't disastrous either.
+    */
+   cast_entry = (plpgsql_CastHashEntry *) hash_search(cast_hash,
+                                                      (void *) &cast_key,
+                                                      HASH_ENTER, &found);
+   Assert(!found);             /* wasn't there a moment ago */
+
+   cast_entry->cast_exprstate = cast_exprstate;
+
+   return cast_exprstate;
+}
 
 /* ----------
  * exec_simple_check_node -        Recursively check if an expression