Re: plpgsql versus domains - Mailing list pgsql-hackers

From Tom Lane
Subject Re: plpgsql versus domains
Date
Msg-id 20331.1425411152@sss.pgh.pa.us
Whole thread Raw
In response to Re: plpgsql versus domains  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: plpgsql versus domains
List pgsql-hackers
I wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Thu, Feb 26, 2015 at 1:53 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> To some extent this is a workaround for the fact that plpgsql does type
>>> conversions the way it does (ie, by I/O rather than by using the parser's
>>> cast mechanisms).  We've talked about changing that, but people seem to
>>> be afraid of the compatibility consequences, and I'm not planning to fight
>>> two compatibility battles concurrently ;-)

>> A sensible plan, but since we're here:

>> - I do agree that changing the way PL/pgsql does those type
>> conversions is scary.  I can't predict what will break, and there's no
>> getting around the scariness of that.

>> - On the other hand, I do think it would be good to change it, because
>> this sucks:

>> rhaas=# do $$ declare x int; begin x := (3.0::numeric)/(1.0::numeric); end $$;
>> ERROR:  invalid input syntax for integer: "3.0000000000000000"
>> CONTEXT:  PL/pgSQL function inline_code_block line 1 at assignment

> If we did want to open that can of worms, my proposal would be to make
> plpgsql type conversions work like so:
> * If there is a cast pathway known to the core SQL parser, use that
>   mechanism.
> * Otherwise, attempt to convert via I/O as we do today.
> This seems to minimize the risk of breaking things, although there would
> probably be corner cases that work differently (for instance I bet boolean
> to text might turn out differently).  In the very long run we could perhaps
> deprecate and remove the second phase.

Since people didn't seem to be running away screaming, here is a patch to
do that.  I've gone through the list of existing casts, and as far as I
can tell the only change in behavior in cases that would have worked
before is the aforementioned boolean->string case.  Before, if you made
plpgsql convert a bool to text, you got 't' or 'f', eg

regression=# do $$declare t text; begin t := true; raise notice 't = %', t; end $$;
NOTICE:  t = t
DO

Now you get 'true' or 'false', because that's what the cast does, eg

regression=# do $$declare t text; begin t := true; raise notice 't = %', t; end $$;
NOTICE:  t = true
DO

This seems to me to be a narrow enough behavioral change that we could
tolerate it in a major release.  (Of course, maybe somebody out there
thinks that failures like the one Robert exhibits are a feature, in
which case they'd have a rather longer list of compatibility issues.)

The performance gain is fairly nifty.  I tested int->bigint coercions
like this:

do $$
declare b bigint;
begin
  for i in 1 .. 1000000 loop
    b := i;
  end loop;
end$$;

On my machine, in non-cassert builds, this takes around 750 ms in 9.4,
610 ms in HEAD (so we already did something good, I'm not quite sure
what), and 420 ms with this patch.  Another example is a no-op domain
conversion:

create domain di int;

do $$
declare b di;
begin
  for i in 1 .. 1000000 loop
    b := i;
  end loop;
end$$;

This takes about 760 ms in 9.4, 660 ms in HEAD, 380 ms with patch.

Comments?

            regards, tom lane

diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index f364ce4..1621254 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** do_compile(FunctionCallInfo fcinfo,
*** 559,566 ****
              {
                  function->fn_retbyval = typeStruct->typbyval;
                  function->fn_rettyplen = typeStruct->typlen;
-                 function->fn_rettypioparam = getTypeIOParam(typeTup);
-                 fmgr_info(typeStruct->typinput, &(function->fn_retinput));

                  /*
                   * install $0 reference, but only for polymorphic return
--- 559,564 ----
*************** plpgsql_compile_inline(char *proc_source
*** 803,809 ****
      char       *func_name = "inline_code_block";
      PLpgSQL_function *function;
      ErrorContextCallback plerrcontext;
-     Oid            typinput;
      PLpgSQL_variable *var;
      int            parse_rc;
      MemoryContext func_cxt;
--- 801,806 ----
*************** plpgsql_compile_inline(char *proc_source
*** 876,883 ****
      /* a bit of hardwired knowledge about type VOID here */
      function->fn_retbyval = true;
      function->fn_rettyplen = sizeof(int32);
-     getTypeInputInfo(VOIDOID, &typinput, &function->fn_rettypioparam);
-     fmgr_info(typinput, &(function->fn_retinput));

      /*
       * Remember if function is STABLE/IMMUTABLE.  XXX would it be better to
--- 873,878 ----
*************** build_datatype(HeapTuple typeTup, int32
*** 2200,2211 ****
      }
      typ->typlen = typeStruct->typlen;
      typ->typbyval = typeStruct->typbyval;
      typ->typrelid = typeStruct->typrelid;
-     typ->typioparam = getTypeIOParam(typeTup);
      typ->collation = typeStruct->typcollation;
      if (OidIsValid(collation) && OidIsValid(typ->collation))
          typ->collation = collation;
-     fmgr_info(typeStruct->typinput, &(typ->typinput));
      typ->atttypmod = typmod;

      return typ;
--- 2195,2205 ----
      }
      typ->typlen = typeStruct->typlen;
      typ->typbyval = typeStruct->typbyval;
+     typ->typisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
      typ->typrelid = typeStruct->typrelid;
      typ->collation = typeStruct->typcollation;
      if (OidIsValid(collation) && OidIsValid(typ->collation))
          typ->collation = collation;
      typ->atttypmod = typmod;

      return typ;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 41a68f8..4b4a442 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 26,31 ****
--- 26,33 ----
  #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"
*************** typedef struct
*** 50,55 ****
--- 52,71 ----
      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 void exec_move_row_from_datum(PLp
*** 211,225 ****
  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,
                  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);
  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);
--- 227,237 ----
  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,
                  Oid valtype, int32 valtypmod,
!                 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);
*************** plpgsql_exec_function(PLpgSQL_function *
*** 454,466 ****
              /* Cast value to proper type */
              estate.retval = exec_cast_value(&estate,
                                              estate.retval,
!                                             fcinfo->isnull,
                                              estate.rettype,
                                              -1,
                                              func->fn_rettype,
!                                             -1,
!                                             &(func->fn_retinput),
!                                             func->fn_rettypioparam);

              /*
               * If the function's return type isn't by value, copy the value
--- 466,476 ----
              /* Cast value to proper type */
              estate.retval = exec_cast_value(&estate,
                                              estate.retval,
!                                             &fcinfo->isnull,
                                              estate.rettype,
                                              -1,
                                              func->fn_rettype,
!                                             -1);

              /*
               * If the function's return type isn't by value, copy the value
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1079,1085 ****
                           * before the notnull check to be consistent with
                           * exec_assign_value.)
                           */
!                         if (!var->datatype->typinput.fn_strict)
                              exec_assign_value(estate,
                                                (PLpgSQL_datum *) var,
                                                (Datum) 0,
--- 1089,1095 ----
                           * before the notnull check to be consistent with
                           * exec_assign_value.)
                           */
!                         if (var->datatype->typisdomain)
                              exec_assign_value(estate,
                                                (PLpgSQL_datum *) var,
                                                (Datum) 0,
*************** exec_stmt_fori(PLpgSQL_execstate *estate
*** 1903,1914 ****
       */
      value = exec_eval_expr(estate, stmt->lower,
                             &isnull, &valtype, &valtypmod);
!     value = exec_cast_value(estate, value, isnull,
                              valtype, valtypmod,
                              var->datatype->typoid,
!                             var->datatype->atttypmod,
!                             &(var->datatype->typinput),
!                             var->datatype->typioparam);
      if (isnull)
          ereport(ERROR,
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
--- 1913,1922 ----
       */
      value = exec_eval_expr(estate, stmt->lower,
                             &isnull, &valtype, &valtypmod);
!     value = exec_cast_value(estate, value, &isnull,
                              valtype, valtypmod,
                              var->datatype->typoid,
!                             var->datatype->atttypmod);
      if (isnull)
          ereport(ERROR,
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
*************** exec_stmt_fori(PLpgSQL_execstate *estate
*** 1921,1932 ****
       */
      value = exec_eval_expr(estate, stmt->upper,
                             &isnull, &valtype, &valtypmod);
!     value = exec_cast_value(estate, value, isnull,
                              valtype, valtypmod,
                              var->datatype->typoid,
!                             var->datatype->atttypmod,
!                             &(var->datatype->typinput),
!                             var->datatype->typioparam);
      if (isnull)
          ereport(ERROR,
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
--- 1929,1938 ----
       */
      value = exec_eval_expr(estate, stmt->upper,
                             &isnull, &valtype, &valtypmod);
!     value = exec_cast_value(estate, value, &isnull,
                              valtype, valtypmod,
                              var->datatype->typoid,
!                             var->datatype->atttypmod);
      if (isnull)
          ereport(ERROR,
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
*************** exec_stmt_fori(PLpgSQL_execstate *estate
*** 1941,1952 ****
      {
          value = exec_eval_expr(estate, stmt->step,
                                 &isnull, &valtype, &valtypmod);
!         value = exec_cast_value(estate, value, isnull,
                                  valtype, valtypmod,
                                  var->datatype->typoid,
!                                 var->datatype->atttypmod,
!                                 &(var->datatype->typinput),
!                                 var->datatype->typioparam);
          if (isnull)
              ereport(ERROR,
                      (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
--- 1947,1956 ----
      {
          value = exec_eval_expr(estate, stmt->step,
                                 &isnull, &valtype, &valtypmod);
!         value = exec_cast_value(estate, value, &isnull,
                                  valtype, valtypmod,
                                  var->datatype->typoid,
!                                 var->datatype->atttypmod);
          if (isnull)
              ereport(ERROR,
                      (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2614,2626 ****
                          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);

                      tuplestore_putvalues(estate->tuple_store, tupdesc,
                                           &retval, &isNull);
--- 2618,2630 ----
                          errmsg("wrong result type supplied in RETURN NEXT")));

                      /* coerce type if needed */
!                     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);
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2740,2752 ****
                         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);

              tuplestore_putvalues(estate->tuple_store, tupdesc,
                                   &retval, &isNull);
--- 2744,2756 ----
                         errmsg("wrong result type supplied in RETURN NEXT")));

              /* coerce type if needed */
!             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);
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4070,4082 ****

                  newvalue = exec_cast_value(estate,
                                             value,
!                                            isNull,
                                             valtype,
                                             valtypmod,
                                             var->datatype->typoid,
!                                            var->datatype->atttypmod,
!                                            &(var->datatype->typinput),
!                                            var->datatype->typioparam);

                  if (isNull && var->notnull)
                      ereport(ERROR,
--- 4074,4084 ----

                  newvalue = exec_cast_value(estate,
                                             value,
!                                            &isNull,
                                             valtype,
                                             valtypmod,
                                             var->datatype->typoid,
!                                            var->datatype->atttypmod);

                  if (isNull && var->notnull)
                      ereport(ERROR,
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4220,4232 ****
                   */
                  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);
                  nulls[fno] = isNull;

                  /*
--- 4222,4234 ----
                   */
                  atttype = rec->tupdesc->attrs[fno]->atttypid;
                  atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
!                 values[fno] = exec_cast_value(estate,
!                                               value,
!                                               &isNull,
!                                               valtype,
!                                               valtypmod,
!                                               atttype,
!                                               atttypmod);
                  nulls[fno] = isNull;

                  /*
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4383,4395 ****
                  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);

                  /*
                   * If the original array is null, cons up an empty array so
--- 4385,4397 ----
                  estate->eval_tuptable = save_eval_tuptable;

                  /* Coerce source value to match array element type. */
!                 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
*************** exec_eval_integer(PLpgSQL_execstate *est
*** 4760,4768 ****
      int32        exprtypmod;

      exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
!     exprdatum = exec_simple_cast_value(estate, exprdatum, *isNull,
!                                        exprtypeid, exprtypmod,
!                                        INT4OID, -1);
      return DatumGetInt32(exprdatum);
  }

--- 4762,4770 ----
      int32        exprtypmod;

      exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
!     exprdatum = exec_cast_value(estate, exprdatum, isNull,
!                                 exprtypeid, exprtypmod,
!                                 INT4OID, -1);
      return DatumGetInt32(exprdatum);
  }

*************** exec_eval_boolean(PLpgSQL_execstate *est
*** 4783,4791 ****
      int32        exprtypmod;

      exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
!     exprdatum = exec_simple_cast_value(estate, exprdatum, *isNull,
!                                        exprtypeid, exprtypmod,
!                                        BOOLOID, -1);
      return DatumGetBool(exprdatum);
  }

--- 4785,4793 ----
      int32        exprtypmod;

      exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
!     exprdatum = exec_cast_value(estate, exprdatum, isNull,
!                                 exprtypeid, exprtypmod,
!                                 BOOLOID, -1);
      return DatumGetBool(exprdatum);
  }

*************** convert_value_to_string(PLpgSQL_execstat
*** 5705,5710 ****
--- 5707,5716 ----
  /* ----------
   * 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 in principle happen.
+  *
   * 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
*************** convert_value_to_string(PLpgSQL_execstat
*** 5713,5723 ****
   */
  static Datum
  exec_cast_value(PLpgSQL_execstate *estate,
!                 Datum value, bool isnull,
                  Oid valtype, int32 valtypmod,
!                 Oid reqtype, int32 reqtypmod,
!                 FmgrInfo *reqinput,
!                 Oid reqtypioparam)
  {
      /*
       * If the type of the given value isn't what's requested, convert it.
--- 5719,5727 ----
   */
  static Datum
  exec_cast_value(PLpgSQL_execstate *estate,
!                 Datum value, bool *isnull,
                  Oid valtype, int32 valtypmod,
!                 Oid reqtype, int32 reqtypmod)
  {
      /*
       * If the type of the given value isn't what's requested, convert it.
*************** exec_cast_value(PLpgSQL_execstate *estat
*** 5725,5791 ****
      if (valtype != reqtype ||
          (valtypmod != reqtypmod && reqtypmod != -1))
      {
!         MemoryContext oldcontext;

!         oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
!         if (!isnull)
          {
!             char       *extval;

!             extval = convert_value_to_string(estate, value, valtype);
!             value = InputFunctionCall(reqinput, extval,
!                                       reqtypioparam, reqtypmod);
!         }
!         else
!         {
!             value = InputFunctionCall(reqinput, NULL,
!                                       reqtypioparam, reqtypmod);
          }
-         MemoryContextSwitchTo(oldcontext);
      }

      return value;
  }

  /* ----------
!  * exec_simple_cast_value            Cast a value if required
   *
!  * 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.
   * ----------
   */
! static Datum
! exec_simple_cast_value(PLpgSQL_execstate *estate,
!                        Datum value, bool isnull,
!                        Oid valtype, int32 valtypmod,
!                        Oid reqtype, int32 reqtypmod)
  {
!     if (valtype != reqtype ||
!         (valtypmod != reqtypmod && reqtypmod != -1))
      {
!         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);
      }

!     return value;
! }


  /* ----------
   * exec_simple_check_node -        Recursively check if an expression
--- 5729,5899 ----
      if (valtype != reqtype ||
          (valtypmod != reqtypmod && reqtypmod != -1))
      {
!         ExprState  *cast_expr;

!         cast_expr = get_cast_expression(estate, valtype, reqtype, reqtypmod);
!         if (cast_expr)
          {
!             ExprContext *econtext = estate->eval_econtext;
!             MemoryContext oldcontext;

!             oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
!             econtext->caseValue_datum = value;
!             econtext->caseValue_isNull = *isnull;
!
!             value = ExecEvalExpr(cast_expr, econtext, isnull, NULL);
!
!             MemoryContextSwitchTo(oldcontext);
          }
      }

      return value;
  }

  /* ----------
!  * get_cast_expression            Look up how to perform a type cast
   *
!  * 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.
!  * In principle this could probably be 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.
   * ----------
   */
! static ExprState *
! get_cast_expression(PLpgSQL_execstate *estate,
!                     Oid srctype, Oid dsttype, int32 dsttypmod)
  {
!     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)
      {
!         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;
!     }

!     /* 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.
!      */
!     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
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 624c91e..8bac860 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 22,27 ****
--- 22,28 ----
  #include "commands/event_trigger.h"
  #include "commands/trigger.h"
  #include "executor/spi.h"
+ #include "utils/hsearch.h"

  /**********************************************************************
   * Definitions
*************** typedef struct
*** 178,187 ****
      int            ttype;            /* PLPGSQL_TTYPE_ code */
      int16        typlen;            /* stuff copied from its pg_type entry */
      bool        typbyval;
      Oid            typrelid;
-     Oid            typioparam;
      Oid            collation;        /* from pg_type, but can be overridden */
-     FmgrInfo    typinput;        /* lookup info for typinput function */
      int32        atttypmod;        /* typmod (taken from someplace else) */
  } PLpgSQL_type;

--- 179,187 ----
      int            ttype;            /* PLPGSQL_TTYPE_ code */
      int16        typlen;            /* stuff copied from its pg_type entry */
      bool        typbyval;
+     bool        typisdomain;
      Oid            typrelid;
      Oid            collation;        /* from pg_type, but can be overridden */
      int32        atttypmod;        /* typmod (taken from someplace else) */
  } PLpgSQL_type;

*************** typedef struct PLpgSQL_function
*** 709,716 ****
      Oid            fn_rettype;
      int            fn_rettyplen;
      bool        fn_retbyval;
-     FmgrInfo    fn_retinput;
-     Oid            fn_rettypioparam;
      bool        fn_retistuple;
      bool        fn_retset;
      bool        fn_readonly;
--- 709,714 ----
*************** typedef struct PLpgSQL_function
*** 748,753 ****
--- 746,754 ----
      PLpgSQL_datum **datums;
      PLpgSQL_stmt_block *action;

+     /* table for performing casts needed in this function */
+     HTAB       *cast_hash;
+
      /* these fields change when the function is used */
      struct PLpgSQL_execstate *cur_estate;
      unsigned long use_count;

pgsql-hackers by date:

Previous
From: Josh Berkus
Date:
Subject: Re: Patch: raise default for max_wal_segments to 1GB
Next
From: Pavel Stehule
Date:
Subject: Re: plpgsql versus domains