I wrote:
> Robert Haas <robertmh...@gmail.com> writes:
>> On Thu, Feb 26, 2015 at 1:53 PM, Tom Lane <t...@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;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to