Skip to content

Commit 0fc94a5

Browse files
committed
Repair mishandling of cached cast-expression trees in plpgsql.
In commit 1345cc6, I introduced caching of expressions representing type-cast operations into plpgsql. However, I supposed that I could cache both the expression trees and the evaluation state trees derived from them for the life of the session. This doesn't work, because we execute the expressions in plpgsql's simple_eval_estate, which has an ecxt_per_query_memory that is only transaction-lifespan. Therefore we can end up putting pointers into the evaluation state tree that point to transaction-lifespan memory; in particular this happens if the cast expression calls a SQL-language function, as reported by Geoff Winkless. The minimum-risk fix seems to be to treat the state trees the same way we do for "simple expression" trees in plpgsql, ie create them in the simple_eval_estate's ecxt_per_query_memory, which means recreating them once per transaction. Since I had to introduce bookkeeping overhead for that anyway, I bought back some of the added cost by sharing the read-only expression trees across all functions in the session, instead of using a per-function table as originally. The simple-expression bookkeeping takes care of the recursive-usage risk that I was concerned about avoiding before. At some point we should take a harder look at how all this works, and see if we can't reduce the amount of tree reinitialization needed. But that won't happen for 9.5.
1 parent 266e771 commit 0fc94a5

File tree

4 files changed

+262
-124
lines changed

4 files changed

+262
-124
lines changed

src/pl/plpgsql/src/pl_exec.c

+170-120
Original file line numberDiff line numberDiff line change
@@ -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
10489
static EState *shared_simple_eval_estate = NULL;
10590
static 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);
243261
static void exec_init_tuple_store(PLpgSQL_execstate *estate);
244262
static void exec_set_found(PLpgSQL_execstate *estate, bool state);
245263
static 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
/* ----------

src/pl/plpgsql/src/plpgsql.h

-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
#include "commands/event_trigger.h"
2323
#include "commands/trigger.h"
2424
#include "executor/spi.h"
25-
#include "utils/hsearch.h"
2625

2726
/**********************************************************************
2827
* Definitions
@@ -760,9 +759,6 @@ typedef struct PLpgSQL_function
760759
/* function body parsetree */
761760
PLpgSQL_stmt_block *action;
762761

763-
/* table for performing casts needed in this function */
764-
HTAB *cast_hash;
765-
766762
/* these fields change when the function is used */
767763
struct PLpgSQL_execstate *cur_estate;
768764
unsigned long use_count;

0 commit comments

Comments
 (0)