Compare commits

...

3 Commits

Author SHA1 Message Date
Tom Lane 441ee1677e Fix memory leakage in plpgsql DO blocks that use cast expressions.
Commit 04fe805a1 modified plpgsql so that datatype casts make use of
expressions cached by plancache.c, in place of older code where these
expression trees were managed by plpgsql itself.  However, I (tgl)
forgot that we use a separate, shorter-lived cast info hashtable in
DO blocks.  The new mechanism thus resulted in session-lifespan
leakage of the plancache data once a DO block containing one or more
casts terminated.  To fix, split the cast hash table into two parts,
one that tracks only the plancache's CachedExpressions and one that
tracks the expression state trees generated from them.  DO blocks need
their own expression state trees and hence their own version of the
second hash table, but there's no reason they can't share the
CachedExpressions with regular plpgsql functions.

Per report from Ajit Awekar.  Back-patch to v12 where the issue
was introduced.

Ajit Awekar and Tom Lane

Discussion: https://postgr.es/m/CAHv6PyrNaqdvyWUspzd3txYQguFTBSnhx+m6tS06TnM+KWc_LQ@mail.gmail.com
2023-04-24 14:19:46 -04:00
Tom Lane fce3b26e97 Rename ExecAggTransReparent, and improve its documentation.
The name of this function suggests that it ought to reparent R/W
expanded objects to be children of the persistent aggcontext, instead
of copying them.  In fact it does no such thing, and if you try to
make it do so you will see multiple regression failures.  Rename it
to the less-misleading ExecAggCopyTransValue, and add commentary
about why that attractive-sounding optimization won't work.  Also
adjust comments at call sites, some of which were describing logic
that has since been moved into ExecAggCopyTransValue.

Discussion: https://postgr.es/m/3004282.1681930251@sss.pgh.pa.us
2023-04-24 13:01:33 -04:00
Peter Eisentraut 5a8366ad75 doc: Update SQL features names
Some feature names have been adjusted over time upstream.  This pulls
in those changes.
2023-04-24 15:39:54 +02:00
9 changed files with 136 additions and 78 deletions

View File

@ -330,21 +330,21 @@ F843 POSITION_REGEX function NO consider regexp_instr()
F844 SUBSTRING_REGEX function NO consider regexp_substr()
F845 TRANSLATE_REGEX function NO consider regexp_replace()
F846 Octet support in regular expression operators NO
F847 Nonconstant regular expressions NO
F850 Top-level <order by clause> in <query expression> YES
F851 <order by clause> in subqueries YES
F852 Top-level <order by clause> in views YES
F855 Nested <order by clause> in <query expression> YES
F856 Nested <fetch first clause> in <query expression> YES
F857 Top-level <fetch first clause> in <query expression> YES
F858 <fetch first clause> in subqueries YES
F859 Top-level <fetch first clause> in views YES
F847 Non-constant regular expressions NO
F850 Top-level ORDER BY in query expression YES
F851 ORDER BY in subqueries YES
F852 Top-level ORDER BY in views YES
F855 Nested ORDER BY in query expression YES
F856 Nested FETCH FIRST in query expression YES
F857 Top-level FETCH FIRST in query expression YES
F858 FETCH FIRST in subqueries YES
F859 Top-level FETCH FIRST in views YES
F860 Dynamic FETCH FIRST row count YES
F861 Top-level <result offset clause> in <query expression> YES
F862 <result offset clause> in subqueries YES
F863 Nested <result offset clause> in <query expression> YES
F864 Top-level <result offset clause> in views YES
F865 <offset row count> in <result offset clause> YES
F861 Top-level OFFSET in query expression YES
F862 OFFSET in subqueries YES
F863 Nested OFFSET in query expression YES
F864 Top-level OFFSET in views YES
F865 Dynamic offset row count in OFFSET YES
F866 FETCH FIRST clause: PERCENT option NO
F867 FETCH FIRST clause: WITH TIES option YES
F868 ORDER BY in grouped table YES
@ -419,7 +419,7 @@ T046 CLOB data type NO
T047 POSITION, OCTET_LENGTH, TRIM, and SUBSTRING for BLOBs NO
T048 Concatenation of BLOBs NO
T049 BLOB locator: non-holdable NO
T050 POSITION, CHAR_LENGTH, OCTET_LENGTH, LOWER, TRIM, UPPER, and SUBSTRING CLOBs NO
T050 POSITION, CHAR_LENGTH, OCTET_LENGTH, LOWER, TRIM, UPPER, and SUBSTRING for CLOBs NO
T051 Row types NO
T053 Explicit aliases for all-fields reference NO
T054 GREATEST and LEAST YES different null handling
@ -487,11 +487,11 @@ T325 Qualified SQL parameter references YES
T326 Table functions NO
T331 Basic roles YES
T332 Extended roles YES
T341 Overloading of SQL-invoked functions and procedures YES
T351 Bracketed SQL comments (/*...*/ comments) YES
T341 Overloading of SQL-invoked functions and SQL-invoked procedures YES
T351 Bracketed comments YES
T431 Extended grouping capabilities YES
T432 Nested and concatenated GROUPING SETS YES
T433 Multiargument GROUPING function YES
T433 Multi-argument GROUPING function YES
T434 GROUP BY DISTINCT YES
T441 ABS and MOD functions YES
T461 Symmetric BETWEEN predicate YES
@ -703,8 +703,8 @@ X152 IS VALID predicate: with CONTENT option NO
X153 IS VALID predicate: with SEQUENCE option NO
X155 IS VALID predicate: NAMESPACE without ELEMENT clause NO
X157 IS VALID predicate: NO NAMESPACE with ELEMENT clause NO
X160 Basic Information Schema for registered XML Schemas NO
X161 Advanced Information Schema for registered XML Schemas NO
X160 Basic Information Schema for registered XML schemas NO
X161 Advanced Information Schema for registered XML schemas NO
X170 XML null handling options NO
X171 NIL ON NO CONTENT option NO
X181 XML(DOCUMENT(UNTYPED)) type NO

View File

@ -4341,15 +4341,40 @@ ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup
}
/*
* Ensure that the current transition value is a child of the aggcontext,
* rather than the per-tuple context.
* Ensure that the new transition value is stored in the aggcontext,
* rather than the per-tuple context. This should be invoked only when
* we know (a) the transition data type is pass-by-reference, and (b)
* the newValue is distinct from the oldValue.
*
* NB: This can change the current memory context.
*
* We copy the presented newValue into the aggcontext, except when the datum
* points to a R/W expanded object that is already a child of the aggcontext,
* in which case we need not copy. We then delete the oldValue, if not null.
*
* If the presented datum points to a R/W expanded object that is a child of
* some other context, ideally we would just reparent it under the aggcontext.
* Unfortunately, that doesn't work easily, and it wouldn't help anyway for
* aggregate-aware transfns. We expect that a transfn that deals in expanded
* objects and is aware of the memory management conventions for aggregate
* transition values will (1) on first call, return a R/W expanded object that
* is already in the right context, allowing us to do nothing here, and (2) on
* subsequent calls, modify and return that same object, so that control
* doesn't even reach here. However, if we have a generic transfn that
* returns a new R/W expanded object (probably in the per-tuple context),
* reparenting that result would cause problems. We'd pass that R/W object to
* the next invocation of the transfn, and then it would be at liberty to
* change or delete that object, and if it deletes it then our own attempt to
* delete the now-old transvalue afterwards would be a double free. We avoid
* this problem by forcing the stored transvalue to always be a flat
* non-expanded object unless the transfn is visibly doing aggregate-aware
* memory management. This is somewhat inefficient, but the best answer to
* that is to write a smarter transfn.
*/
Datum
ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
Datum newValue, bool newValueIsNull,
Datum oldValue, bool oldValueIsNull)
ExecAggCopyTransValue(AggState *aggstate, AggStatePerTrans pertrans,
Datum newValue, bool newValueIsNull,
Datum oldValue, bool oldValueIsNull)
{
Assert(newValue != oldValue);
@ -4559,12 +4584,10 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
/*
* For pass-by-ref datatype, must copy the new value into aggcontext and
* free the prior transValue. But if transfn returned a pointer to its
* first input, we don't need to do anything. Also, if transfn returned a
* pointer to a R/W expanded object that is already a child of the
* aggcontext, assume we can adopt that value without copying it.
* first input, we don't need to do anything.
*
* It's safe to compare newVal with pergroup->transValue without regard
* for either being NULL, because ExecAggTransReparent() takes care to set
* for either being NULL, because ExecAggCopyTransValue takes care to set
* transValue to 0 when NULL. Otherwise we could end up accidentally not
* reparenting, when the transValue has the same numerical value as
* newValue, despite being NULL. This is a somewhat hot path, making it
@ -4573,10 +4596,10 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
* argument.
*/
if (DatumGetPointer(newVal) != DatumGetPointer(pergroup->transValue))
newVal = ExecAggTransReparent(aggstate, pertrans,
newVal, fcinfo->isnull,
pergroup->transValue,
pergroup->transValueIsNull);
newVal = ExecAggCopyTransValue(aggstate, pertrans,
newVal, fcinfo->isnull,
pergroup->transValue,
pergroup->transValueIsNull);
pergroup->transValue = newVal;
pergroup->transValueIsNull = fcinfo->isnull;

View File

@ -778,12 +778,10 @@ advance_transition_function(AggState *aggstate,
/*
* If pass-by-ref datatype, must copy the new value into aggcontext and
* free the prior transValue. But if transfn returned a pointer to its
* first input, we don't need to do anything. Also, if transfn returned a
* pointer to a R/W expanded object that is already a child of the
* aggcontext, assume we can adopt that value without copying it.
* first input, we don't need to do anything.
*
* It's safe to compare newVal with pergroup->transValue without regard
* for either being NULL, because ExecAggTransReparent() takes care to set
* for either being NULL, because ExecAggCopyTransValue takes care to set
* transValue to 0 when NULL. Otherwise we could end up accidentally not
* reparenting, when the transValue has the same numerical value as
* newValue, despite being NULL. This is a somewhat hot path, making it
@ -793,10 +791,10 @@ advance_transition_function(AggState *aggstate,
*/
if (!pertrans->transtypeByVal &&
DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue))
newVal = ExecAggTransReparent(aggstate, pertrans,
newVal, fcinfo->isnull,
pergroupstate->transValue,
pergroupstate->transValueIsNull);
newVal = ExecAggCopyTransValue(aggstate, pertrans,
newVal, fcinfo->isnull,
pergroupstate->transValue,
pergroupstate->transValueIsNull);
pergroupstate->transValue = newVal;
pergroupstate->transValueIsNull = fcinfo->isnull;

View File

@ -368,7 +368,8 @@ advance_windowaggregate(WindowAggState *winstate,
* free the prior transValue. But if transfn returned a pointer to its
* first input, we don't need to do anything. Also, if transfn returned a
* pointer to a R/W expanded object that is already a child of the
* aggcontext, assume we can adopt that value without copying it.
* aggcontext, assume we can adopt that value without copying it. (See
* comments for ExecAggCopyTransValue, which this code duplicates.)
*/
if (!peraggstate->transtypeByVal &&
DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
@ -533,7 +534,8 @@ advance_windowaggregate_base(WindowAggState *winstate,
* free the prior transValue. But if invtransfn returned a pointer to its
* first input, we don't need to do anything. Also, if invtransfn
* returned a pointer to a R/W expanded object that is already a child of
* the aggcontext, assume we can adopt that value without copying it.
* the aggcontext, assume we can adopt that value without copying it. (See
* comments for ExecAggCopyTransValue, which this code duplicates.)
*
* Note: the checks for null values here will never fire, but it seems
* best to have this stanza look just like advance_windowaggregate.

View File

@ -2314,7 +2314,7 @@ llvm_compile_expr(ExprState *state)
params[5] = LLVMBuildTrunc(b, v_transnull,
TypeParamBool, "");
v_fn = llvm_pg_func(mod, "ExecAggTransReparent");
v_fn = llvm_pg_func(mod, "ExecAggCopyTransValue");
v_newval =
LLVMBuildCall(b, v_fn,
params, lengthof(params),

View File

@ -102,7 +102,7 @@ FunctionReturningBool(void)
void *referenced_functions[] =
{
ExecAggInitGroup,
ExecAggTransReparent,
ExecAggCopyTransValue,
ExecEvalPreOrderedDistinctSingle,
ExecEvalPreOrderedDistinctMulti,
ExecEvalAggOrderedTransDatum,

View File

@ -807,9 +807,9 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
Datum newValue, bool newValueIsNull,
Datum oldValue, bool oldValueIsNull);
extern Datum ExecAggCopyTransValue(AggState *aggstate, AggStatePerTrans pertrans,
Datum newValue, bool newValueIsNull,
Datum oldValue, bool oldValueIsNull);
extern bool ExecEvalPreOrderedDistinctSingle(AggState *aggstate,
AggStatePerTrans pertrans);
extern bool ExecEvalPreOrderedDistinctMulti(AggState *aggstate,

View File

@ -136,18 +136,22 @@ static ResourceOwner shared_simple_eval_resowner = NULL;
MemoryContextAllocZero(get_eval_mcontext(estate), sz)
/*
* We use a session-wide hash table for caching cast information.
* We use two session-wide hash tables for caching cast information.
*
* Once built, the compiled expression trees (cast_expr fields) survive for
* the life of the session. At some point it might be worth invalidating
* those after pg_cast changes, but for the moment we don't bother.
* cast_expr_hash entries (of type plpgsql_CastExprHashEntry) hold compiled
* expression trees for casts. These survive for the life of the session and
* are shared across all PL/pgSQL functions and DO blocks. At some point it
* might be worth invalidating them after pg_cast changes, but for the moment
* we don't bother.
*
* The evaluation state trees (cast_exprstate) are managed in the same way as
* simple expressions (i.e., we assume cast expressions are always simple).
* There is a separate hash table shared_cast_hash (with entries of type
* plpgsql_CastHashEntry) containing evaluation state trees for these
* expressions, which are managed in the same way as simple expressions
* (i.e., we assume cast expressions are always simple).
*
* As with simple expressions, DO blocks don't use the shared hash table but
* must have their own. This isn't ideal, but we don't want to deal with
* multiple simple_eval_estates within a DO block.
* As with simple expressions, DO blocks don't use the shared_cast_hash table
* but must have their own evaluation state trees. This isn't ideal, but we
* don't want to deal with multiple simple_eval_estates within a DO block.
*/
typedef struct /* lookup key for cast info */
{
@ -158,18 +162,24 @@ typedef struct /* lookup key for cast info */
int32 dsttypmod; /* destination typmod for cast */
} plpgsql_CastHashKey;
typedef struct /* cast_hash table entry */
typedef struct /* cast_expr_hash table entry */
{
plpgsql_CastHashKey key; /* hash key --- MUST BE FIRST */
Expr *cast_expr; /* cast expression, or NULL if no-op cast */
CachedExpression *cast_cexpr; /* cached expression backing the above */
} plpgsql_CastExprHashEntry;
typedef struct /* cast_hash table entry */
{
plpgsql_CastHashKey key; /* hash key --- MUST BE FIRST */
plpgsql_CastExprHashEntry *cast_centry; /* link to matching expr entry */
/* ExprState is valid only when cast_lxid matches current LXID */
ExprState *cast_exprstate; /* expression's eval tree */
bool cast_in_use; /* true while we're executing eval tree */
LocalTransactionId cast_lxid;
} plpgsql_CastHashEntry;
static MemoryContext shared_cast_context = NULL;
static HTAB *cast_expr_hash = NULL;
static HTAB *shared_cast_hash = NULL;
/*
@ -4025,6 +4035,17 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->paramLI->parserSetupArg = NULL; /* filled during use */
estate->paramLI->numParams = estate->ndatums;
/* Create the session-wide cast-expression hash if we didn't already */
if (cast_expr_hash == NULL)
{
ctl.keysize = sizeof(plpgsql_CastHashKey);
ctl.entrysize = sizeof(plpgsql_CastExprHashEntry);
cast_expr_hash = hash_create("PLpgSQL cast expressions",
16, /* start small and extend */
&ctl,
HASH_ELEM | HASH_BLOBS);
}
/* set up for use of appropriate simple-expression EState and cast hash */
if (simple_eval_estate)
{
@ -4037,7 +4058,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
16, /* start small and extend */
&ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
estate->cast_hash_context = CurrentMemoryContext;
}
else
{
@ -4045,19 +4065,14 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
/* Create the session-wide cast-info hash table if we didn't already */
if (shared_cast_hash == NULL)
{
shared_cast_context = AllocSetContextCreate(TopMemoryContext,
"PLpgSQL cast info",
ALLOCSET_DEFAULT_SIZES);
ctl.keysize = sizeof(plpgsql_CastHashKey);
ctl.entrysize = sizeof(plpgsql_CastHashEntry);
ctl.hcxt = shared_cast_context;
shared_cast_hash = hash_create("PLpgSQL cast cache",
16, /* start small and extend */
&ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
HASH_ELEM | HASH_BLOBS);
}
estate->cast_hash = shared_cast_hash;
estate->cast_hash_context = shared_cast_context;
}
/* likewise for the simple-expression resource owner */
if (simple_eval_resowner)
@ -7768,6 +7783,7 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
{
plpgsql_CastHashKey cast_key;
plpgsql_CastHashEntry *cast_entry;
plpgsql_CastExprHashEntry *expr_entry;
bool found;
LocalTransactionId curlxid;
MemoryContext oldcontext;
@ -7781,10 +7797,28 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
&cast_key,
HASH_ENTER, &found);
if (!found) /* initialize if new entry */
cast_entry->cast_cexpr = NULL;
{
/* We need a second lookup to see if a cast_expr_hash entry exists */
expr_entry = (plpgsql_CastExprHashEntry *) hash_search(cast_expr_hash,
&cast_key,
HASH_ENTER,
&found);
if (!found) /* initialize if new expr entry */
expr_entry->cast_cexpr = NULL;
if (cast_entry->cast_cexpr == NULL ||
!cast_entry->cast_cexpr->is_valid)
cast_entry->cast_centry = expr_entry;
cast_entry->cast_exprstate = NULL;
cast_entry->cast_in_use = false;
cast_entry->cast_lxid = InvalidLocalTransactionId;
}
else
{
/* Use always-valid link to avoid a second hash lookup */
expr_entry = cast_entry->cast_centry;
}
if (expr_entry->cast_cexpr == NULL ||
!expr_entry->cast_cexpr->is_valid)
{
/*
* We've not looked up this coercion before, or we have but the cached
@ -7797,10 +7831,10 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
/*
* Drop old cached expression if there is one.
*/
if (cast_entry->cast_cexpr)
if (expr_entry->cast_cexpr)
{
FreeCachedExpression(cast_entry->cast_cexpr);
cast_entry->cast_cexpr = NULL;
FreeCachedExpression(expr_entry->cast_cexpr);
expr_entry->cast_cexpr = NULL;
}
/*
@ -7881,9 +7915,11 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
((RelabelType *) cast_expr)->arg == (Expr *) placeholder)
cast_expr = NULL;
/* Now we can fill in the hashtable entry. */
cast_entry->cast_cexpr = cast_cexpr;
cast_entry->cast_expr = (Expr *) cast_expr;
/* Now we can fill in the expression hashtable entry. */
expr_entry->cast_cexpr = cast_cexpr;
expr_entry->cast_expr = (Expr *) cast_expr;
/* Be sure to reset the exprstate hashtable entry, too. */
cast_entry->cast_exprstate = NULL;
cast_entry->cast_in_use = false;
cast_entry->cast_lxid = InvalidLocalTransactionId;
@ -7892,7 +7928,7 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
}
/* Done if we have determined that this is a no-op cast. */
if (cast_entry->cast_expr == NULL)
if (expr_entry->cast_expr == NULL)
return NULL;
/*
@ -7911,7 +7947,7 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
if (cast_entry->cast_lxid != curlxid || cast_entry->cast_in_use)
{
oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
cast_entry->cast_exprstate = ExecInitExpr(cast_entry->cast_expr, NULL);
cast_entry->cast_exprstate = ExecInitExpr(expr_entry->cast_expr, NULL);
cast_entry->cast_in_use = false;
cast_entry->cast_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);

View File

@ -1076,7 +1076,6 @@ typedef struct PLpgSQL_execstate
/* lookup table to use for executing type casts */
HTAB *cast_hash;
MemoryContext cast_hash_context;
/* memory context for statement-lifespan temporary values */
MemoryContext stmt_mcontext; /* current stmt context, or NULL if none */