@@ -53,21 +53,6 @@ typedef struct
5353 bool * freevals ; /* which arguments are pfree-able */
5454} PreparedParamsData ;
5555
56- typedef struct
57- {
58- /* NB: we assume this struct contains no padding bytes */
59- Oid srctype ; /* source type for cast */
60- Oid dsttype ; /* destination type for cast */
61- int32 srctypmod ; /* source typmod for cast */
62- int32 dsttypmod ; /* destination typmod for cast */
63- } plpgsql_CastHashKey ;
64-
65- typedef struct
66- {
67- plpgsql_CastHashKey key ; /* hash key --- MUST BE FIRST */
68- ExprState * cast_exprstate ; /* cast expression, or NULL if no-op cast */
69- } plpgsql_CastHashEntry ;
70-
7156/*
7257 * All plpgsql function executions within a single transaction share the same
7358 * executor EState for evaluating "simple" expressions. Each function call
@@ -104,6 +89,38 @@ typedef struct SimpleEcontextStackEntry
10489static EState * shared_simple_eval_estate = NULL ;
10590static SimpleEcontextStackEntry * simple_econtext_stack = NULL ;
10691
92+ /*
93+ * We use a session-wide hash table for caching cast information.
94+ *
95+ * Once built, the compiled expression trees (cast_expr fields) survive for
96+ * the life of the session. At some point it might be worth invalidating
97+ * those after pg_cast changes, but for the moment we don't bother.
98+ *
99+ * The evaluation state trees (cast_exprstate) are managed in the same way as
100+ * simple expressions (i.e., we assume cast expressions are always simple).
101+ */
102+ typedef struct /* lookup key for cast info */
103+ {
104+ /* NB: we assume this struct contains no padding bytes */
105+ Oid srctype ; /* source type for cast */
106+ Oid dsttype ; /* destination type for cast */
107+ int32 srctypmod ; /* source typmod for cast */
108+ int32 dsttypmod ; /* destination typmod for cast */
109+ } plpgsql_CastHashKey ;
110+
111+ typedef struct /* cast_hash table entry */
112+ {
113+ plpgsql_CastHashKey key ; /* hash key --- MUST BE FIRST */
114+ Expr * cast_expr ; /* cast expression, or NULL if no-op cast */
115+ /* The ExprState tree is valid only when cast_lxid matches current LXID */
116+ ExprState * cast_exprstate ; /* expression's eval tree */
117+ bool cast_in_use ; /* true while we're executing eval tree */
118+ LocalTransactionId cast_lxid ;
119+ } plpgsql_CastHashEntry ;
120+
121+ static MemoryContext cast_hash_context = NULL ;
122+ static HTAB * cast_hash = NULL ;
123+
107124/************************************************************
108125 * Local function forward declarations
109126 ************************************************************/
@@ -238,8 +255,9 @@ static Datum exec_cast_value(PLpgSQL_execstate *estate,
238255 Datum value , bool * isnull ,
239256 Oid valtype , int32 valtypmod ,
240257 Oid reqtype , int32 reqtypmod );
241- static ExprState * get_cast_expression (PLpgSQL_execstate * estate ,
242- Oid srctype , int32 srctypmod , Oid dsttype , int32 dsttypmod );
258+ static plpgsql_CastHashEntry * get_cast_hashentry (PLpgSQL_execstate * estate ,
259+ Oid srctype , int32 srctypmod ,
260+ Oid dsttype , int32 dsttypmod );
243261static void exec_init_tuple_store (PLpgSQL_execstate * estate );
244262static void exec_set_found (PLpgSQL_execstate * estate , bool state );
245263static void plpgsql_create_econtext (PLpgSQL_execstate * estate );
@@ -6043,12 +6061,12 @@ exec_cast_value(PLpgSQL_execstate *estate,
60436061 if (valtype != reqtype ||
60446062 (valtypmod != reqtypmod && reqtypmod != -1 ))
60456063 {
6046- ExprState * cast_expr ;
6064+ plpgsql_CastHashEntry * cast_entry ;
60476065
6048- cast_expr = get_cast_expression (estate ,
6066+ cast_entry = get_cast_hashentry (estate ,
60496067 valtype , valtypmod ,
60506068 reqtype , reqtypmod );
6051- if (cast_expr )
6069+ if (cast_entry )
60526070 {
60536071 ExprContext * econtext = estate -> eval_econtext ;
60546072 MemoryContext oldcontext ;
@@ -6058,7 +6076,12 @@ exec_cast_value(PLpgSQL_execstate *estate,
60586076 econtext -> caseValue_datum = value ;
60596077 econtext -> caseValue_isNull = * isnull ;
60606078
6061- value = ExecEvalExpr (cast_expr , econtext , isnull , NULL );
6079+ cast_entry -> cast_in_use = true;
6080+
6081+ value = ExecEvalExpr (cast_entry -> cast_exprstate , econtext ,
6082+ isnull , NULL );
6083+
6084+ cast_entry -> cast_in_use = false;
60626085
60636086 MemoryContextSwitchTo (oldcontext );
60646087 }
@@ -6068,46 +6091,44 @@ exec_cast_value(PLpgSQL_execstate *estate,
60686091}
60696092
60706093/* ----------
6071- * get_cast_expression Look up how to perform a type cast
6072- *
6073- * Returns an expression evaluation tree based on a CaseTestExpr input,
6074- * or NULL if the cast is a mere no-op relabeling.
6094+ * get_cast_hashentry Look up how to perform a type cast
60756095 *
6076- * We cache the results of the lookup in a per-function hash table.
6077- * It's tempting to consider using a session-wide hash table instead,
6078- * but that introduces some corner-case questions that probably aren't
6079- * worth dealing with; in particular that re-entrant use of an evaluation
6080- * tree might occur. That would also set in stone the assumption that
6081- * collation isn't important to a cast function.
6096+ * Returns a plpgsql_CastHashEntry if an expression has to be evaluated,
6097+ * or NULL if the cast is a mere no-op relabeling. If there's work to be
6098+ * done, the cast_exprstate field contains an expression evaluation tree
6099+ * based on a CaseTestExpr input, and the cast_in_use field should be set
6100+ * TRUE while executing it.
60826101 * ----------
60836102 */
6084- static ExprState *
6085- get_cast_expression (PLpgSQL_execstate * estate ,
6086- Oid srctype , int32 srctypmod , Oid dsttype , int32 dsttypmod )
6103+ static plpgsql_CastHashEntry *
6104+ get_cast_hashentry (PLpgSQL_execstate * estate ,
6105+ Oid srctype , int32 srctypmod ,
6106+ Oid dsttype , int32 dsttypmod )
60876107{
6088- HTAB * cast_hash = estate -> func -> cast_hash ;
60896108 plpgsql_CastHashKey cast_key ;
60906109 plpgsql_CastHashEntry * cast_entry ;
60916110 bool found ;
6092- CaseTestExpr * placeholder ;
6093- Node * cast_expr ;
6094- ExprState * cast_exprstate ;
6111+ LocalTransactionId curlxid ;
60956112 MemoryContext oldcontext ;
60966113
6097- /* Create the cast-info hash table if we didn't already */
6114+ /* Create the session-wide cast-info hash table if we didn't already */
60986115 if (cast_hash == NULL )
60996116 {
61006117 HASHCTL ctl ;
61016118
6119+ cast_hash_context = AllocSetContextCreate (TopMemoryContext ,
6120+ "PLpgSQL cast info" ,
6121+ ALLOCSET_DEFAULT_MINSIZE ,
6122+ ALLOCSET_DEFAULT_INITSIZE ,
6123+ ALLOCSET_DEFAULT_MAXSIZE );
61026124 memset (& ctl , 0 , sizeof (ctl ));
61036125 ctl .keysize = sizeof (plpgsql_CastHashKey );
61046126 ctl .entrysize = sizeof (plpgsql_CastHashEntry );
6105- ctl .hcxt = estate -> func -> fn_cxt ;
6127+ ctl .hcxt = cast_hash_context ;
61066128 cast_hash = hash_create ("PLpgSQL cast cache" ,
61076129 16 , /* start small and extend */
61086130 & ctl ,
61096131 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT );
6110- estate -> func -> cast_hash = cast_hash ;
61116132 }
61126133
61136134 /* Look for existing entry */
@@ -6118,102 +6139,131 @@ get_cast_expression(PLpgSQL_execstate *estate,
61186139 cast_entry = (plpgsql_CastHashEntry * ) hash_search (cast_hash ,
61196140 (void * ) & cast_key ,
61206141 HASH_FIND , NULL );
6121- if (cast_entry )
6122- return cast_entry -> cast_exprstate ;
61236142
6124- /* Construct expression tree for coercion in function's context */
6125- oldcontext = MemoryContextSwitchTo (estate -> func -> fn_cxt );
6143+ if (cast_entry == NULL )
6144+ {
6145+ /* We've not looked up this coercion before */
6146+ Node * cast_expr ;
6147+ CaseTestExpr * placeholder ;
61266148
6127- /*
6128- * We use a CaseTestExpr as the base of the coercion tree, since it's very
6129- * cheap to insert the source value for that.
6130- */
6131- placeholder = makeNode (CaseTestExpr );
6132- placeholder -> typeId = srctype ;
6133- placeholder -> typeMod = srctypmod ;
6134- placeholder -> collation = get_typcollation (srctype );
6135- if (OidIsValid (estate -> func -> fn_input_collation ) &&
6136- OidIsValid (placeholder -> collation ))
6137- placeholder -> collation = estate -> func -> fn_input_collation ;
6149+ /*
6150+ * Since we could easily fail (no such coercion), construct a
6151+ * temporary coercion expression tree in a short-lived context, then
6152+ * if successful copy it to cast_hash_context.
6153+ */
6154+ oldcontext = MemoryContextSwitchTo (estate -> eval_econtext -> ecxt_per_tuple_memory );
61386155
6139- /*
6140- * Apply coercion. We use ASSIGNMENT coercion because that's the closest
6141- * match to plpgsql's historical behavior; in particular, EXPLICIT
6142- * coercion would allow silent truncation to a destination
6143- * varchar/bpchar's length, which we do not want.
6144- *
6145- * If source type is UNKNOWN, coerce_to_target_type will fail (it only
6146- * expects to see that for Const input nodes), so don't call it; we'll
6147- * apply CoerceViaIO instead. Likewise, it doesn't currently work for
6148- * coercing RECORD to some other type, so skip for that too.
6149- */
6150- if (srctype == UNKNOWNOID || srctype == RECORDOID )
6151- cast_expr = NULL ;
6152- else
6153- cast_expr = coerce_to_target_type (NULL ,
6154- (Node * ) placeholder , srctype ,
6155- dsttype , dsttypmod ,
6156- COERCION_ASSIGNMENT ,
6157- COERCE_IMPLICIT_CAST ,
6158- -1 );
6156+ /*
6157+ * We use a CaseTestExpr as the base of the coercion tree, since it's
6158+ * very cheap to insert the source value for that.
6159+ */
6160+ placeholder = makeNode (CaseTestExpr );
6161+ placeholder -> typeId = srctype ;
6162+ placeholder -> typeMod = srctypmod ;
6163+ placeholder -> collation = get_typcollation (srctype );
61596164
6160- /*
6161- * If there's no cast path according to the parser, fall back to using an
6162- * I/O coercion; this is semantically dubious but matches plpgsql's
6163- * historical behavior. We would need something of the sort for UNKNOWN
6164- * literals in any case.
6165- */
6166- if (cast_expr == NULL )
6167- {
6168- CoerceViaIO * iocoerce = makeNode (CoerceViaIO );
6169-
6170- iocoerce -> arg = (Expr * ) placeholder ;
6171- iocoerce -> resulttype = dsttype ;
6172- iocoerce -> resultcollid = InvalidOid ;
6173- iocoerce -> coerceformat = COERCE_IMPLICIT_CAST ;
6174- iocoerce -> location = -1 ;
6175- cast_expr = (Node * ) iocoerce ;
6176- if (dsttypmod != -1 )
6165+ /*
6166+ * Apply coercion. We use ASSIGNMENT coercion because that's the
6167+ * closest match to plpgsql's historical behavior; in particular,
6168+ * EXPLICIT coercion would allow silent truncation to a destination
6169+ * varchar/bpchar's length, which we do not want.
6170+ *
6171+ * If source type is UNKNOWN, coerce_to_target_type will fail (it only
6172+ * expects to see that for Const input nodes), so don't call it; we'll
6173+ * apply CoerceViaIO instead. Likewise, it doesn't currently work for
6174+ * coercing RECORD to some other type, so skip for that too.
6175+ */
6176+ if (srctype == UNKNOWNOID || srctype == RECORDOID )
6177+ cast_expr = NULL ;
6178+ else
61776179 cast_expr = coerce_to_target_type (NULL ,
6178- cast_expr , dsttype ,
6180+ ( Node * ) placeholder , srctype ,
61796181 dsttype , dsttypmod ,
61806182 COERCION_ASSIGNMENT ,
61816183 COERCE_IMPLICIT_CAST ,
61826184 -1 );
6183- }
61846185
6185- /* Note: we don't bother labeling the expression tree with collation */
6186+ /*
6187+ * If there's no cast path according to the parser, fall back to using
6188+ * an I/O coercion; this is semantically dubious but matches plpgsql's
6189+ * historical behavior. We would need something of the sort for
6190+ * UNKNOWN literals in any case.
6191+ */
6192+ if (cast_expr == NULL )
6193+ {
6194+ CoerceViaIO * iocoerce = makeNode (CoerceViaIO );
6195+
6196+ iocoerce -> arg = (Expr * ) placeholder ;
6197+ iocoerce -> resulttype = dsttype ;
6198+ iocoerce -> resultcollid = InvalidOid ;
6199+ iocoerce -> coerceformat = COERCE_IMPLICIT_CAST ;
6200+ iocoerce -> location = -1 ;
6201+ cast_expr = (Node * ) iocoerce ;
6202+ if (dsttypmod != -1 )
6203+ cast_expr = coerce_to_target_type (NULL ,
6204+ cast_expr , dsttype ,
6205+ dsttype , dsttypmod ,
6206+ COERCION_ASSIGNMENT ,
6207+ COERCE_IMPLICIT_CAST ,
6208+ -1 );
6209+ }
6210+
6211+ /* Note: we don't bother labeling the expression tree with collation */
61866212
6187- /* Detect whether we have a no-op (RelabelType) coercion */
6188- if (IsA (cast_expr , RelabelType ) &&
6189- ((RelabelType * ) cast_expr )-> arg == (Expr * ) placeholder )
6190- cast_expr = NULL ;
6213+ /* Detect whether we have a no-op (RelabelType) coercion */
6214+ if (IsA (cast_expr , RelabelType ) &&
6215+ ((RelabelType * ) cast_expr )-> arg == (Expr * ) placeholder )
6216+ cast_expr = NULL ;
61916217
6192- if (cast_expr )
6193- {
6194- /* ExecInitExpr assumes we've planned the expression */
6195- cast_expr = (Node * ) expression_planner ((Expr * ) cast_expr );
6196- /* Create an expression eval state tree for it */
6197- cast_exprstate = ExecInitExpr ((Expr * ) cast_expr , NULL );
6218+ if (cast_expr )
6219+ {
6220+ /* ExecInitExpr assumes we've planned the expression */
6221+ cast_expr = (Node * ) expression_planner ((Expr * ) cast_expr );
6222+
6223+ /* Now copy the tree into cast_hash_context */
6224+ MemoryContextSwitchTo (cast_hash_context );
6225+
6226+ cast_expr = copyObject (cast_expr );
6227+ }
6228+
6229+ MemoryContextSwitchTo (oldcontext );
6230+
6231+ /* Now we can fill in a hashtable entry. */
6232+ cast_entry = (plpgsql_CastHashEntry * ) hash_search (cast_hash ,
6233+ (void * ) & cast_key ,
6234+ HASH_ENTER , & found );
6235+ Assert (!found ); /* wasn't there a moment ago */
6236+ cast_entry -> cast_expr = (Expr * ) cast_expr ;
6237+ cast_entry -> cast_exprstate = NULL ;
6238+ cast_entry -> cast_in_use = false;
6239+ cast_entry -> cast_lxid = InvalidLocalTransactionId ;
61986240 }
6199- else
6200- cast_exprstate = NULL ;
62016241
6202- MemoryContextSwitchTo (oldcontext );
6242+ /* Done if we have determined that this is a no-op cast. */
6243+ if (cast_entry -> cast_expr == NULL )
6244+ return NULL ;
62036245
62046246 /*
6205- * Now fill in a hashtable entry. If we fail anywhere up to/including
6206- * this step, we've only leaked some memory in the function context, which
6207- * isn't great but isn't disastrous either.
6208- */
6209- cast_entry = (plpgsql_CastHashEntry * ) hash_search (cast_hash ,
6210- (void * ) & cast_key ,
6211- HASH_ENTER , & found );
6212- Assert (!found ); /* wasn't there a moment ago */
6213-
6214- cast_entry -> cast_exprstate = cast_exprstate ;
6247+ * Prepare the expression for execution, if it's not been done already in
6248+ * the current transaction; also, if it's marked busy in the current
6249+ * transaction, abandon that expression tree and build a new one, so as to
6250+ * avoid potential problems with recursive cast expressions and failed
6251+ * executions. (We will leak some memory intra-transaction if that
6252+ * happens a lot, but we don't expect it to.) It's okay to update the
6253+ * hash table with the new tree because all plpgsql functions within a
6254+ * given transaction share the same simple_eval_estate.
6255+ */
6256+ curlxid = MyProc -> lxid ;
6257+ if (cast_entry -> cast_lxid != curlxid || cast_entry -> cast_in_use )
6258+ {
6259+ oldcontext = MemoryContextSwitchTo (estate -> simple_eval_estate -> es_query_cxt );
6260+ cast_entry -> cast_exprstate = ExecInitExpr (cast_entry -> cast_expr , NULL );
6261+ cast_entry -> cast_in_use = false;
6262+ cast_entry -> cast_lxid = curlxid ;
6263+ MemoryContextSwitchTo (oldcontext );
6264+ }
62156265
6216- return cast_exprstate ;
6266+ return cast_entry ;
62176267}
62186268
62196269/* ----------
0 commit comments