Commit 1efc7e53 authored by Robert Haas's avatar Robert Haas

Fix problems with ParamListInfo serialization mechanism.

Commit d1b7c1ff introduced a mechanism
for serializing a ParamListInfo structure to be passed to a parallel
worker.  However, this mechanism failed to handle external expanded
values, as pointed out by Noah Misch.  Repair.

Moreover, plpgsql_param_fetch requires adjustment because the
serialization mechanism needs it to skip evaluating unused parameters
just as we would do when it is called from copyParamList, but params
== estate->paramLI in that case.  To fix, make the bms_is_member test
in that function unconditional.

Finally, have setup_param_list set a new ParamListInfo field,
paramMask, to the parameters actually used in the expression, so that
we don't try to fetch those that are not needed when serializing a
parameter list.  This isn't necessary for correctness, but it makes
the performance of the parallel executor code comparable to what we
do for cases involving cursors.

Design suggestions and extensive review by Noah Misch.  Patch by me.
parent bf25fb2f
...@@ -392,6 +392,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params, ...@@ -392,6 +392,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
paramLI->parserSetup = NULL; paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL; paramLI->parserSetupArg = NULL;
paramLI->numParams = num_params; paramLI->numParams = num_params;
paramLI->paramMask = NULL;
i = 0; i = 0;
foreach(l, exprstates) foreach(l, exprstates)
......
...@@ -910,6 +910,7 @@ postquel_sub_params(SQLFunctionCachePtr fcache, ...@@ -910,6 +910,7 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
paramLI->parserSetup = NULL; paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL; paramLI->parserSetupArg = NULL;
paramLI->numParams = nargs; paramLI->numParams = nargs;
paramLI->paramMask = NULL;
fcache->paramLI = paramLI; fcache->paramLI = paramLI;
} }
else else
......
...@@ -2330,6 +2330,7 @@ _SPI_convert_params(int nargs, Oid *argtypes, ...@@ -2330,6 +2330,7 @@ _SPI_convert_params(int nargs, Oid *argtypes,
paramLI->parserSetup = NULL; paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL; paramLI->parserSetupArg = NULL;
paramLI->numParams = nargs; paramLI->numParams = nargs;
paramLI->paramMask = NULL;
for (i = 0; i < nargs; i++) for (i = 0; i < nargs; i++)
{ {
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "postgres.h" #include "postgres.h"
#include "nodes/bitmapset.h"
#include "nodes/params.h" #include "nodes/params.h"
#include "storage/shmem.h" #include "storage/shmem.h"
#include "utils/datum.h" #include "utils/datum.h"
...@@ -50,6 +51,7 @@ copyParamList(ParamListInfo from) ...@@ -50,6 +51,7 @@ copyParamList(ParamListInfo from)
retval->parserSetup = NULL; retval->parserSetup = NULL;
retval->parserSetupArg = NULL; retval->parserSetupArg = NULL;
retval->numParams = from->numParams; retval->numParams = from->numParams;
retval->paramMask = NULL;
for (i = 0; i < from->numParams; i++) for (i = 0; i < from->numParams; i++)
{ {
...@@ -58,6 +60,17 @@ copyParamList(ParamListInfo from) ...@@ -58,6 +60,17 @@ copyParamList(ParamListInfo from)
int16 typLen; int16 typLen;
bool typByVal; bool typByVal;
/* Ignore parameters we don't need, to save cycles and space. */
if (retval->paramMask != NULL &&
!bms_is_member(i, retval->paramMask))
{
nprm->value = (Datum) 0;
nprm->isnull = true;
nprm->pflags = 0;
nprm->ptype = InvalidOid;
continue;
}
/* give hook a chance in case parameter is dynamic */ /* give hook a chance in case parameter is dynamic */
if (!OidIsValid(oprm->ptype) && from->paramFetch != NULL) if (!OidIsValid(oprm->ptype) && from->paramFetch != NULL)
(*from->paramFetch) (from, i + 1); (*from->paramFetch) (from, i + 1);
...@@ -90,19 +103,28 @@ EstimateParamListSpace(ParamListInfo paramLI) ...@@ -90,19 +103,28 @@ EstimateParamListSpace(ParamListInfo paramLI)
for (i = 0; i < paramLI->numParams; i++) for (i = 0; i < paramLI->numParams; i++)
{ {
ParamExternData *prm = &paramLI->params[i]; ParamExternData *prm = &paramLI->params[i];
Oid typeOid;
int16 typLen; int16 typLen;
bool typByVal; bool typByVal;
/* Ignore parameters we don't need, to save cycles and space. */
if (paramLI->paramMask != NULL &&
!bms_is_member(i, paramLI->paramMask))
typeOid = InvalidOid;
else
{
/* give hook a chance in case parameter is dynamic */ /* give hook a chance in case parameter is dynamic */
if (!OidIsValid(prm->ptype) && paramLI->paramFetch != NULL) if (!OidIsValid(prm->ptype) && paramLI->paramFetch != NULL)
(*paramLI->paramFetch) (paramLI, i + 1); (*paramLI->paramFetch) (paramLI, i + 1);
typeOid = prm->ptype;
}
sz = add_size(sz, sizeof(Oid)); /* space for type OID */ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
sz = add_size(sz, sizeof(uint16)); /* space for pflags */ sz = add_size(sz, sizeof(uint16)); /* space for pflags */
/* space for datum/isnull */ /* space for datum/isnull */
if (OidIsValid(prm->ptype)) if (OidIsValid(typeOid))
get_typlenbyval(prm->ptype, &typLen, &typByVal); get_typlenbyval(typeOid, &typLen, &typByVal);
else else
{ {
/* If no type OID, assume by-value, like copyParamList does. */ /* If no type OID, assume by-value, like copyParamList does. */
...@@ -150,15 +172,24 @@ SerializeParamList(ParamListInfo paramLI, char **start_address) ...@@ -150,15 +172,24 @@ SerializeParamList(ParamListInfo paramLI, char **start_address)
for (i = 0; i < nparams; i++) for (i = 0; i < nparams; i++)
{ {
ParamExternData *prm = &paramLI->params[i]; ParamExternData *prm = &paramLI->params[i];
Oid typeOid;
int16 typLen; int16 typLen;
bool typByVal; bool typByVal;
/* Ignore parameters we don't need, to save cycles and space. */
if (paramLI->paramMask != NULL &&
!bms_is_member(i, paramLI->paramMask))
typeOid = InvalidOid;
else
{
/* give hook a chance in case parameter is dynamic */ /* give hook a chance in case parameter is dynamic */
if (!OidIsValid(prm->ptype) && paramLI->paramFetch != NULL) if (!OidIsValid(prm->ptype) && paramLI->paramFetch != NULL)
(*paramLI->paramFetch) (paramLI, i + 1); (*paramLI->paramFetch) (paramLI, i + 1);
typeOid = prm->ptype;
}
/* Write type OID. */ /* Write type OID. */
memcpy(*start_address, &prm->ptype, sizeof(Oid)); memcpy(*start_address, &typeOid, sizeof(Oid));
*start_address += sizeof(Oid); *start_address += sizeof(Oid);
/* Write flags. */ /* Write flags. */
...@@ -166,8 +197,8 @@ SerializeParamList(ParamListInfo paramLI, char **start_address) ...@@ -166,8 +197,8 @@ SerializeParamList(ParamListInfo paramLI, char **start_address)
*start_address += sizeof(uint16); *start_address += sizeof(uint16);
/* Write datum/isnull. */ /* Write datum/isnull. */
if (OidIsValid(prm->ptype)) if (OidIsValid(typeOid))
get_typlenbyval(prm->ptype, &typLen, &typByVal); get_typlenbyval(typeOid, &typLen, &typByVal);
else else
{ {
/* If no type OID, assume by-value, like copyParamList does. */ /* If no type OID, assume by-value, like copyParamList does. */
...@@ -209,6 +240,7 @@ RestoreParamList(char **start_address) ...@@ -209,6 +240,7 @@ RestoreParamList(char **start_address)
paramLI->parserSetup = NULL; paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL; paramLI->parserSetupArg = NULL;
paramLI->numParams = nparams; paramLI->numParams = nparams;
paramLI->paramMask = NULL;
for (i = 0; i < nparams; i++) for (i = 0; i < nparams; i++)
{ {
......
...@@ -1629,6 +1629,7 @@ exec_bind_message(StringInfo input_message) ...@@ -1629,6 +1629,7 @@ exec_bind_message(StringInfo input_message)
params->parserSetup = NULL; params->parserSetup = NULL;
params->parserSetupArg = NULL; params->parserSetupArg = NULL;
params->numParams = numParams; params->numParams = numParams;
params->paramMask = NULL;
for (paramno = 0; paramno < numParams; paramno++) for (paramno = 0; paramno < numParams; paramno++)
{ {
......
...@@ -264,6 +264,11 @@ datumEstimateSpace(Datum value, bool isnull, bool typByVal, int typLen) ...@@ -264,6 +264,11 @@ datumEstimateSpace(Datum value, bool isnull, bool typByVal, int typLen)
/* no need to use add_size, can't overflow */ /* no need to use add_size, can't overflow */
if (typByVal) if (typByVal)
sz += sizeof(Datum); sz += sizeof(Datum);
else if (VARATT_IS_EXTERNAL_EXPANDED(value))
{
ExpandedObjectHeader *eoh = DatumGetEOHP(value);
sz += EOH_get_flat_size(eoh);
}
else else
sz += datumGetSize(value, typByVal, typLen); sz += datumGetSize(value, typByVal, typLen);
} }
...@@ -292,6 +297,7 @@ void ...@@ -292,6 +297,7 @@ void
datumSerialize(Datum value, bool isnull, bool typByVal, int typLen, datumSerialize(Datum value, bool isnull, bool typByVal, int typLen,
char **start_address) char **start_address)
{ {
ExpandedObjectHeader *eoh = NULL;
int header; int header;
/* Write header word. */ /* Write header word. */
...@@ -299,6 +305,11 @@ datumSerialize(Datum value, bool isnull, bool typByVal, int typLen, ...@@ -299,6 +305,11 @@ datumSerialize(Datum value, bool isnull, bool typByVal, int typLen,
header = -2; header = -2;
else if (typByVal) else if (typByVal)
header = -1; header = -1;
else if (VARATT_IS_EXTERNAL_EXPANDED(value))
{
eoh = DatumGetEOHP(value);
header = EOH_get_flat_size(eoh);
}
else else
header = datumGetSize(value, typByVal, typLen); header = datumGetSize(value, typByVal, typLen);
memcpy(*start_address, &header, sizeof(int)); memcpy(*start_address, &header, sizeof(int));
...@@ -312,6 +323,11 @@ datumSerialize(Datum value, bool isnull, bool typByVal, int typLen, ...@@ -312,6 +323,11 @@ datumSerialize(Datum value, bool isnull, bool typByVal, int typLen,
memcpy(*start_address, &value, sizeof(Datum)); memcpy(*start_address, &value, sizeof(Datum));
*start_address += sizeof(Datum); *start_address += sizeof(Datum);
} }
else if (eoh)
{
EOH_flatten_into(eoh, (void *) *start_address, header);
*start_address += header;
}
else else
{ {
memcpy(*start_address, DatumGetPointer(value), header); memcpy(*start_address, DatumGetPointer(value), header);
......
...@@ -14,7 +14,8 @@ ...@@ -14,7 +14,8 @@
#ifndef PARAMS_H #ifndef PARAMS_H
#define PARAMS_H #define PARAMS_H
/* To avoid including a pile of parser headers, reference ParseState thus: */ /* Forward declarations, to avoid including other headers */
struct Bitmapset;
struct ParseState; struct ParseState;
...@@ -71,6 +72,7 @@ typedef struct ParamListInfoData ...@@ -71,6 +72,7 @@ typedef struct ParamListInfoData
ParserSetupHook parserSetup; /* parser setup hook */ ParserSetupHook parserSetup; /* parser setup hook */
void *parserSetupArg; void *parserSetupArg;
int numParams; /* number of ParamExternDatas following */ int numParams; /* number of ParamExternDatas following */
struct Bitmapset *paramMask; /* if non-NULL, can ignore omitted params */
ParamExternData params[FLEXIBLE_ARRAY_MEMBER]; ParamExternData params[FLEXIBLE_ARRAY_MEMBER];
} ParamListInfoData; } ParamListInfoData;
......
...@@ -3287,6 +3287,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate, ...@@ -3287,6 +3287,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup; estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
estate->paramLI->parserSetupArg = NULL; /* filled during use */ estate->paramLI->parserSetupArg = NULL; /* filled during use */
estate->paramLI->numParams = estate->ndatums; estate->paramLI->numParams = estate->ndatums;
estate->paramLI->paramMask = NULL;
estate->params_dirty = false; estate->params_dirty = false;
/* set up for use of appropriate simple-expression EState and cast hash */ /* set up for use of appropriate simple-expression EState and cast hash */
...@@ -5558,6 +5559,12 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) ...@@ -5558,6 +5559,12 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
*/ */
paramLI->parserSetupArg = (void *) expr; paramLI->parserSetupArg = (void *) expr;
/*
* Allow parameters that aren't needed by this expression to be
* ignored.
*/
paramLI->paramMask = expr->paramnos;
/* /*
* Also make sure this is set before parser hooks need it. There is * Also make sure this is set before parser hooks need it. There is
* no need to save and restore, since the value is always correct once * no need to save and restore, since the value is always correct once
...@@ -5592,6 +5599,9 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) ...@@ -5592,6 +5599,9 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
* shared param list, where it could get passed to some less-trusted function. * shared param list, where it could get passed to some less-trusted function.
* *
* Caller should pfree the result after use, if it's not NULL. * Caller should pfree the result after use, if it's not NULL.
*
* XXX. Could we use ParamListInfo's new paramMask to avoid creating unshared
* parameter lists?
*/ */
static ParamListInfo static ParamListInfo
setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
...@@ -5623,6 +5633,7 @@ setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) ...@@ -5623,6 +5633,7 @@ setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup; paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
paramLI->parserSetupArg = (void *) expr; paramLI->parserSetupArg = (void *) expr;
paramLI->numParams = estate->ndatums; paramLI->numParams = estate->ndatums;
paramLI->paramMask = NULL;
/* /*
* Instantiate values for "safe" parameters of the expression. We * Instantiate values for "safe" parameters of the expression. We
...@@ -5696,25 +5707,20 @@ plpgsql_param_fetch(ParamListInfo params, int paramid) ...@@ -5696,25 +5707,20 @@ plpgsql_param_fetch(ParamListInfo params, int paramid)
/* now we can access the target datum */ /* now we can access the target datum */
datum = estate->datums[dno]; datum = estate->datums[dno];
/* need to behave slightly differently for shared and unshared arrays */
if (params != estate->paramLI)
{
/* /*
* We're being called, presumably from copyParamList(), for cursor * Since copyParamList() or SerializeParamList() will try to materialize
* parameters. Since copyParamList() will try to materialize every * every single parameter slot, it's important to do nothing when asked
* single parameter slot, it's important to do nothing when asked for * for a datum that's not supposed to be used by this SQL expression.
* a datum that's not supposed to be used by this SQL expression. * Otherwise we risk failures in exec_eval_datum(), or copying a lot more
* Otherwise we risk failures in exec_eval_datum(), not to mention * data than necessary.
* possibly copying a lot more data than the cursor actually uses.
*/ */
if (!bms_is_member(dno, expr->paramnos)) if (!bms_is_member(dno, expr->paramnos))
return; return;
}
else if (params == estate->paramLI)
{ {
/* /*
* Normal evaluation cases. We don't need to sanity-check dno, but we * We need to mark the shared params array dirty if we're about to
* do need to mark the shared params array dirty if we're about to
* evaluate a resettable datum. * evaluate a resettable datum.
*/ */
switch (datum->dtype) switch (datum->dtype)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment