Nested CASE-WHEN scoping
While looking at fixing the multiple-evaluation issue in IN and BETWEEN
discussed a while ago, I realized that the current assumption that only
one CaseTestExpr placeholder needs to be valid at any given time is not
true.
Here's a bit contrived example:
CREATE FUNCTION evileq (timestamptz, int4) returns boolean AS $$
SELECT case $2 WHEN length($1::text) THEN true ELSE false END;
$$ language sql;
CREATE OPERATOR = (procedure = evileq, leftarg = timestamptz, rightarg =
int4);
postgres=# SELECT now() = 29, CASE now() WHEN 29 THEN 'foo' ELSE 'bar' END;
?column? | case
----------+------
t | bar
(1 row)
Direct call to the operator, "now () = 29" returns true, but when used
in CASE-WHEN, which implicitly does the same comparison, the result is
false. Admittedly that's pretty far-fetched, but nevertheless it's a bug.
As part of the BETWEEN/IN fix, I was going to refactor CaseTestExpr and
CoerceToDomainValue placeholder node types into one generic placeholder
node. BETWEEN needs three placeholder slots in the worst case [*], and
now it seems that we need to handle an arbitrary number of simultaneous
placeholders even for CASE-WHEN.
So I'm going to put the BETWEEN/IN fix aside for now, and refactor the
placeholder infrastructure to handle several simultaneous placeholders,
and replace CaseTestExpr and CoerceToDomainValue with it. Actually
AggRef and WindowFunc nodes look a lot like CaseTestExpr and
CoerceToDomainValue too, but I'm a bit scared of touching those.
PS. This is all 9.2 material, in case you're wondering. We're talking
about pretty big patches.
[*] a BETWEEN SYMMETRIC b AND c is handled as "(a <= b AND a >= c) OR (a
= b AND a <= c)", leading to multiple evaluation of all three operands
if placeholders are not used
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On 25.05.2011 14:57, Heikki Linnakangas wrote:
Here's a bit contrived example:
CREATE FUNCTION evileq (timestamptz, int4) returns boolean AS $$
SELECT case $2 WHEN length($1::text) THEN true ELSE false END;
$$ language sql;
CREATE OPERATOR = (procedure = evileq, leftarg = timestamptz, rightarg =
int4);postgres=# SELECT now() = 29, CASE now() WHEN 29 THEN 'foo' ELSE 'bar' END;
?column? | case
----------+------
t | bar
(1 row)Direct call to the operator, "now () = 29" returns true, but when used
in CASE-WHEN, which implicitly does the same comparison, the result is
false. Admittedly that's pretty far-fetched, but nevertheless it's a bug.
I should add that this works fine if the function is not an SQL function
that gets inlined. But inlining is desirable, we don't want to give up
on that, and inhibiting it in that case would need some extra
bookkeeping anyway.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
While looking at fixing the multiple-evaluation issue in IN and BETWEEN
discussed a while ago, I realized that the current assumption that only
one CaseTestExpr placeholder needs to be valid at any given time is not
true.
[ scratches head ... ] Why does the save/restore in ExecEvalCase not
take care of this?
So I'm going to put the BETWEEN/IN fix aside for now, and refactor the
placeholder infrastructure to handle several simultaneous placeholders,
That sounds like a mess, and I'm not even convinced it'll solve the
problem ...
regards, tom lane
On 25.05.2011 17:47, Tom Lane wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
While looking at fixing the multiple-evaluation issue in IN and BETWEEN
discussed a while ago, I realized that the current assumption that only
one CaseTestExpr placeholder needs to be valid at any given time is not
true.[ scratches head ... ] Why does the save/restore in ExecEvalCase not
take care of this?
The mistake happens during planning, when the SQL function is inlined
and pre-evaluated. It's a bit hard to see what happened once the
planning is finished because the whole expression is folded into a
constant, but here's goes:
The original expression is:
CASE now() WHEN 29 THEN 'foo' ELSE 'bar' END;
In parse analysis, it is turned into this:
CASE WHEN CaseTestExpr = 29 THEN 'foo' ELSE 'bar' END;
where CaseTestExpr stands for the now(). Next the planner tries to
simplify the WHEN condition, "CaseTestExpr = 29". The equality operator
is implemented by the evileq(timestamptz, int4) SQL function, defined as:
CASE $2 WHEN length($1::text) THEN true ELSE false END;
That SQL-function is transformed at parse analysis into:
CASE CaseTestExpr = length($1::text) THEN true ELSE false END;
This CaseTestExpr stands for the Param to the function, $2. When that
tranformed SQL function body is inlined into the outer WHEN clause,
"CaseTestExpr = 29", and Params are substituted, it becomes:
CASE CaseTestExpr = length(CaseTestExpr::text) THEN true ELSE false END.
(you can see the expression tree for that if you print out 'newexpr' in
inline_function(), just after the call to substitute_actual_parameters())
At this point it's easy to see that we have screwed up. The first
CaseTestExpr stands for the inner CASE-value, which is $2, which stands
for 29, and the second CaseTestExpr stands for the *outer* CASE-value,
which is supposed to be now(). The planner cannot distinguish between
the two anymore.
Both CaseTestExprs are then incorrectly replaced with constant 29, and
the whole expression is constant-folded into 'bar'.
So I'm going to put the BETWEEN/IN fix aside for now, and refactor the
placeholder infrastructure to handle several simultaneous placeholders,That sounds like a mess, and I'm not even convinced it'll solve the
problem ...
Hmm, it would solve the above by if we can keep the CaseTestExprs
separate. It's not quite as straightforward as I originally thought, as
the parse analysis of the inlined SQL function needs to use placeholder
numbers that are different from those used in the outer context. But it
seems doable.
BTW, i just stumbled into this:
postgres=# explain verbose SELECT CASE now() WHEN (29+random()::int4)
THEN 'foo' ELSE 'bar' END;
ERROR: unexpected CASE WHEN clause: 326
Looks like ruleutils.c is also not prepared for the case that the
implicit equality operation gets inlined into something else than an OpExpr.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
On 25.05.2011 17:47, Tom Lane wrote:
[ scratches head ... ] Why does the save/restore in ExecEvalCase not
take care of this?
The mistake happens during planning, when the SQL function is inlined
and pre-evaluated.
Hm. I'm inclined to think this may be more of a bug in the inlining
process than anything else. I have to run off for a doctor's
appointment, but will look at this closer later today.
regards, tom lane
On 25.05.2011 20:11, Tom Lane wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
On 25.05.2011 17:47, Tom Lane wrote:
[ scratches head ... ] Why does the save/restore in ExecEvalCase not
take care of this?The mistake happens during planning, when the SQL function is inlined
and pre-evaluated.Hm. I'm inclined to think this may be more of a bug in the inlining
process than anything else.
Well, if you want to get away without the capability to reference
multiple CaseTestExprs at a time, you'll have to detect the danger and
abort the inlining process. That's a bit pessimal, although this is a
pretty artificial case in the first place so maybe we don't care much.
(I'm still going to need more placeholder slots to handle IN and
BETWEEN. Of course, I can just copy-paste CaseTestExpr into something
like InTestExpr and BetweenTestExpr, but it seems like it would be good
to unite all that infrastructure)
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
On 25.05.2011 17:47, Tom Lane wrote:
[ scratches head ... ] Why does the save/restore in ExecEvalCase not
take care of this?
The mistake happens during planning, when the SQL function is inlined
and pre-evaluated.
OK, I see the problem now: we have a CaseTestExpr in the arguments of
the function, which we are unable to reduce to a constant, so it gets
substituted as-is into the body of the function during inlining. And
then it's physically inside the CASE expression that's in the function
body, so it looks like it syntactically belongs to that expression,
which it doesn't. You're probably right that this is impractical to fix
without redesigning the expression representation, and that
CoerceToDomainValue has got a similar issue.
My advice is to not change the parser output representation, though.
What I think you ought to do about this is to have the planner replace
CaseTestExpr and CoerceToDomainValue with PARAM_EXEC Params during
expression preprocessing, and assign suitable Param numbers which it
sticks into the CaseExpr (resp CoerceToDomainExpr) so that those
expressions know which Param slot to fill at runtime. The
const-simplification logic can avoid getting dumber by treating the
cases like known-Param-value cases. I don't think you need to invent
something separate from the PARAM_EXEC infrastructure to handle these.
The main annoyance here is that standalone expressions may now need
Param slots to execute, which will complicate places that need to
evaluate them, but there's probably no way around that (a bespoke
mechanism would complicate those callers just as much, if the number
of value slots it needs is variable, which it will be).
BTW, i just stumbled into this:
postgres=# explain verbose SELECT CASE now() WHEN (29+random()::int4)
THEN 'foo' ELSE 'bar' END;
ERROR: unexpected CASE WHEN clause: 326
Looks like ruleutils.c is also not prepared for the case that the
implicit equality operation gets inlined into something else than an OpExpr.
Grumble ... I thought we'd fixed that ...
regards, tom lane
I wrote:
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
BTW, i just stumbled into this:
postgres=# explain verbose SELECT CASE now() WHEN (29+random()::int4)
THEN 'foo' ELSE 'bar' END;
ERROR: unexpected CASE WHEN clause: 326
Looks like ruleutils.c is also not prepared for the case that the
implicit equality operation gets inlined into something else than an OpExpr.
Grumble ... I thought we'd fixed that ...
Yeah, you're right. We've hacked that code so it can handle some
transformations that the optimizer might apply, but inlining some random
expression to replace the equality operator is far beyond what we can
hope to deal with.
For those following along at home, the point is that if the user writes
CASE test_expr WHEN cmp_expr THEN ...
the parser identifies the equality operator to use and produces
something that looks like this:
CASE test_expr WHEN CaseTestExpr = cmp_expr THEN ...
We really need ruleutils.c to generate the original form when it is
looking at a stored rule (eg a view), so it goes to some lengths to
recognize "CaseTestExpr = something" in a WHEN clause and only print the
"something". However, this example shows that there's no chance of
always being able to do that when looking at an expression that's been
through the planner.
I think what we'd better do, if we don't see something that looks like
"CaseTestExpr = something", is just print whatever we do have in the
WHEN clause. That will require inventing a print representation for
CaseTestExpr, since in most cases that's going to appear in there
somewhere. I suggest we just print CASE_TEST_EXPR, but if anyone wants
to bikeshed, feel free ...
Note that if Heikki does what I suggested upthread, the display will
eventually probably look like "$nn" instead (since it'll be a Param not
a CaseTestExpr). But that's 9.2 or later material.
regards, tom lane
On 26.05.2011 00:31, Tom Lane wrote:
The main annoyance here is that standalone expressions may now need
Param slots to execute, which will complicate places that need to
evaluate them, but there's probably no way around that
Yeah, I can see two complications:
1. Domain constraints
Domain constraint check expressions are fetched and constant-folded
separately from the enclosing expression, in ExecInitExpr(). In order to
allocate distinct paramids for any CASE values within the domain check
expression, we'd need to know how many paramids are in use in the parent
expression. We could dig into the parent PlanState and its EState to get
that, but we don't have that for stand-alone expressions.
2. Index expressions
Index expressions are stored in relcache after constant evaluation, so
any CaseTestExprs will already be replaced with Params. When the recheck
expression is evaluated, the paramids used in the recheck expression
will clash with real PARAM_EXEC params used to pass values up and down
subqueries, as well as any params used for CASEs.
I think we can work around both of those by just saving and restoring
the value of each Param that we set while evaluating an expression, as
the values should not be used simultaneously, but it makes me a bit
uncomfortable. If we ever try to inline those expressions into other
expressions, we'll be right back to the issue we have with nested CASE
now. I'm not sure if we might already do that for index recheck
expressions. Alternatively, we could adjust the paramids when an
expression is inlined into another, similar to what OffsetVarNodes does
for Vars.
For debugging purposes, it seems like a good idea to invent a new kind
of Param for these, and keep them separate from PARAM_EXEC params. The
scope would be different, PARAM_EXECs are used to pass values between
planner nodes, while these new kind of Params would be used to pass
values within a single expression.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
I think we can work around both of those by just saving and restoring
the value of each Param that we set while evaluating an expression,
Huh? That's a waste of time and effort. Just make sure that each such
spot has its own Param number. That's why I'm telling you to do it in
the planner, not the parser --- it is easy to assign globally-unique-
across-the-plan numbers at plan time, in fact we do it already.
For debugging purposes, it seems like a good idea to invent a new kind
of Param for these, and keep them separate from PARAM_EXEC params.
I'd vote against that too. PARAM_EXEC Params already have more than one
purpose.
regards, tom lane
On 30.05.2011 17:21, Tom Lane wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
I think we can work around both of those by just saving and restoring
the value of each Param that we set while evaluating an expression,Huh? That's a waste of time and effort. Just make sure that each such
spot has its own Param number. That's why I'm telling you to do it in
the planner, not the parser --- it is easy to assign globally-unique-
across-the-plan numbers at plan time, in fact we do it already.
Yeah, I got that part. What I'm saying is that it's not that easy,
because of the two issues, domain constraints and index expressions,
that I mentioned. Here's a WIP patch, but those two issues have not been
addressed yet. I'm sure those are not insurmountable problems, I'm just
trying to figure out the best way to surmount them:
For domain constraints, ExecInitExpr could assign globally-unique param
numbers if it knew how many params are in use. That's trivial for
expressions in plans, as you have access to the EState via the
PlanState, and EState can include the number of params assigned. For a
stand-alone expression, we don't have that. There is no global
information whatsoever for stand-alone expressions, ExecInitExpr only
sees the current node it's dealing with. Perhaps we need to add the
concept of a global plan
For index expressions, we could use a function similar to
ChangeVarNodes(), that shifts all the paramids in the already-planned
expression, preparing it for inclusion within the enclosing plan. I'm a
bit worried that that might screw up the logic used to compare if an
expression matches the index expression, though; the param ids in the
two expressions won't match.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Attachments:
replace-casetestexpr-with-param-1.patchtext/x-diff; name=replace-casetestexpr-with-param-1.patchDownload
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 158,163 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
--- 158,164 ----
*/
estate->es_param_list_info = queryDesc->params;
+ estate->es_num_param_exec_vals = queryDesc->plannedstmt->nParamExec;
if (queryDesc->plannedstmt->nParamExec > 0)
estate->es_param_exec_vals = (ParamExecData *)
palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
***************
*** 2179,2184 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
--- 2180,2186 ----
{
int i = parentestate->es_plannedstmt->nParamExec;
+ estate->es_num_param_exec_vals = i;
estate->es_param_exec_vals = (ParamExecData *)
palloc0(i * sizeof(ParamExecData));
while (--i >= 0)
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 61,67 ****
static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
- static bool isAssignmentIndirectionExpr(ExprState *exprstate);
static Datum ExecEvalAggref(AggrefExprState *aggref,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
--- 61,66 ----
***************
*** 78,83 **** static Datum ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
--- 77,83 ----
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+ static void enlargeParamExecVals(ExprContext *econtext, int maxparamid);
static Datum ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext,
***************
*** 122,130 **** static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
- static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
- ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalArray(ArrayExprState *astate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
--- 122,127 ----
***************
*** 352,394 **** ExecEvalArrayRef(ArrayRefExprState *astate,
if (isAssignment)
{
Datum sourceData;
! Datum save_datum;
! bool save_isNull;
/*
* We might have a nested-assignment situation, in which the
* refassgnexpr is itself a FieldStore or ArrayRef that needs to
* obtain and modify the previous value of the array element or slice
* being replaced. If so, we have to extract that value from the
! * array and pass it down via the econtext's caseValue. It's safe to
! * reuse the CASE mechanism because there cannot be a CASE between
! * here and where the value would be needed, and an array assignment
! * can't be within a CASE either. (So saving and restoring the
! * caseValue is just paranoia, but let's do it anyway.)
*
* Since fetching the old element might be a nontrivial expense, do it
* only if the argument appears to actually need it.
*/
! save_datum = econtext->caseValue_datum;
! save_isNull = econtext->caseValue_isNull;
!
! if (isAssignmentIndirectionExpr(astate->refassgnexpr))
{
if (*isNull)
{
/* whole array is null, so any element or slice is too */
! econtext->caseValue_datum = (Datum) 0;
! econtext->caseValue_isNull = true;
}
else if (lIndex == NULL)
{
! econtext->caseValue_datum = array_ref(array_source, i,
! upper.indx,
! astate->refattrlength,
! astate->refelemlength,
! astate->refelembyval,
! astate->refelemalign,
! &econtext->caseValue_isNull);
}
else
{
--- 349,385 ----
if (isAssignment)
{
Datum sourceData;
! ParamExecData *prm;
/*
* We might have a nested-assignment situation, in which the
* refassgnexpr is itself a FieldStore or ArrayRef that needs to
* obtain and modify the previous value of the array element or slice
* being replaced. If so, we have to extract that value from the
! * array and pass it down via an executor Param.
*
* Since fetching the old element might be a nontrivial expense, do it
* only if the argument appears to actually need it.
*/
! if (arrayRef->refparamid != -1)
{
+ enlargeParamExecVals(econtext, arrayRef->refparamid);
+ prm = &econtext->ecxt_param_exec_vals[arrayRef->refparamid];
if (*isNull)
{
/* whole array is null, so any element or slice is too */
! prm->value = (Datum) 0;
! prm->isnull = true;
}
else if (lIndex == NULL)
{
! prm->value = array_ref(array_source, i,
! upper.indx,
! astate->refattrlength,
! astate->refelemlength,
! astate->refelembyval,
! astate->refelemalign,
! &prm->isnull);
}
else
{
***************
*** 398,413 **** ExecEvalArrayRef(ArrayRefExprState *astate,
astate->refelemlength,
astate->refelembyval,
astate->refelemalign);
! econtext->caseValue_datum = PointerGetDatum(resultArray);
! econtext->caseValue_isNull = false;
}
}
- else
- {
- /* argument shouldn't need caseValue, but for safety set it null */
- econtext->caseValue_datum = (Datum) 0;
- econtext->caseValue_isNull = true;
- }
/*
* Evaluate the value to be assigned into the array.
--- 389,398 ----
astate->refelemlength,
astate->refelembyval,
astate->refelemalign);
! prm->value = PointerGetDatum(resultArray);
! prm->isnull = false;
}
}
/*
* Evaluate the value to be assigned into the array.
***************
*** 417,425 **** ExecEvalArrayRef(ArrayRefExprState *astate,
&eisnull,
NULL);
- econtext->caseValue_datum = save_datum;
- econtext->caseValue_isNull = save_isNull;
-
/*
* For an assignment to a fixed-length array type, both the original
* array and the value to be assigned into it must be non-NULL, else
--- 402,407 ----
***************
*** 481,515 **** ExecEvalArrayRef(ArrayRefExprState *astate,
}
}
- /*
- * Helper for ExecEvalArrayRef: is expr a nested FieldStore or ArrayRef
- * that might need the old element value passed down?
- *
- * (We could use this in ExecEvalFieldStore too, but in that case passing
- * the old value is so cheap there's no need.)
- */
- static bool
- isAssignmentIndirectionExpr(ExprState *exprstate)
- {
- if (exprstate == NULL)
- return false; /* just paranoia */
- if (IsA(exprstate, FieldStoreState))
- {
- FieldStore *fstore = (FieldStore *) exprstate->expr;
-
- if (fstore->arg && IsA(fstore->arg, CaseTestExpr))
- return true;
- }
- else if (IsA(exprstate, ArrayRefExprState))
- {
- ArrayRef *arrayRef = (ArrayRef *) exprstate->expr;
-
- if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr))
- return true;
- }
- return false;
- }
-
/* ----------------------------------------------------------------
* ExecEvalAggref
*
--- 463,468 ----
***************
*** 963,968 **** ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
--- 916,941 ----
}
/* ----------------------------------------------------------------
+ * enlargeParamExecVals
+ *
+ * Makes sure that econtext->exec_param_vals is large enough
+ * to hold value for given paramid, enlarging the array if
+ * necessary.
+ * ----------------------------------------------------------------
+ */
+ static void
+ enlargeParamExecVals(ExprContext *econtext, int maxparamid)
+ {
+ if (maxparamid >= econtext->num_param_exec_vals)
+ {
+ econtext->ecxt_param_exec_vals =
+ repalloc(econtext->ecxt_param_exec_vals,
+ sizeof(ParamExecData) * (maxparamid + 1));
+ econtext->num_param_exec_vals = maxparamid + 1;
+ }
+ }
+
+ /* ----------------------------------------------------------------
* ExecEvalParamExec
*
* Returns the value of a PARAM_EXEC parameter.
***************
*** 983,988 **** ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
--- 956,962 ----
* PARAM_EXEC params (internal executor parameters) are stored in the
* ecxt_param_exec_vals array, and can be accessed by array index.
*/
+ Assert(thisParamId < econtext->num_param_exec_vals);
prm = &(econtext->ecxt_param_exec_vals[thisParamId]);
if (prm->execPlan != NULL)
{
***************
*** 2769,2795 **** ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
{
List *clauses = caseExpr->args;
ListCell *clause;
- Datum save_datum;
- bool save_isNull;
if (isDone)
*isDone = ExprSingleResult;
/*
* If there's a test expression, we have to evaluate it and save the value
! * where the CaseTestExpr placeholders can find it. We must save and
! * restore prior setting of econtext's caseValue fields, in case this node
! * is itself within a larger CASE.
*/
- save_datum = econtext->caseValue_datum;
- save_isNull = econtext->caseValue_isNull;
-
if (caseExpr->arg)
{
! econtext->caseValue_datum = ExecEvalExpr(caseExpr->arg,
! econtext,
! &econtext->caseValue_isNull,
! NULL);
}
/*
--- 2743,2766 ----
{
List *clauses = caseExpr->args;
ListCell *clause;
if (isDone)
*isDone = ExprSingleResult;
/*
* If there's a test expression, we have to evaluate it and save the value
! * in the right Param slot.
*/
if (caseExpr->arg)
{
! ParamExecData *prm;
!
! enlargeParamExecVals(econtext, caseExpr->paramid);
! prm = &econtext->ecxt_param_exec_vals[caseExpr->paramid];
! prm->value = ExecEvalExpr(caseExpr->arg,
! econtext,
! &prm->isnull,
! NULL);
}
/*
***************
*** 2814,2821 **** ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
*/
if (DatumGetBool(clause_value) && !*isNull)
{
- econtext->caseValue_datum = save_datum;
- econtext->caseValue_isNull = save_isNull;
return ExecEvalExpr(wclause->result,
econtext,
isNull,
--- 2785,2790 ----
***************
*** 2823,2831 **** ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
}
}
- econtext->caseValue_datum = save_datum;
- econtext->caseValue_isNull = save_isNull;
-
if (caseExpr->defresult)
{
return ExecEvalExpr(caseExpr->defresult,
--- 2792,2797 ----
***************
*** 2838,2859 **** ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
return (Datum) 0;
}
- /*
- * ExecEvalCaseTestExpr
- *
- * Return the value stored by CASE.
- */
- static Datum
- ExecEvalCaseTestExpr(ExprState *exprstate,
- ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone)
- {
- if (isDone)
- *isDone = ExprSingleResult;
- *isNull = econtext->caseValue_isNull;
- return econtext->caseValue_datum;
- }
-
/* ----------------------------------------------------------------
* ExecEvalArray - ARRAY[] expressions
* ----------------------------------------------------------------
--- 2804,2809 ----
***************
*** 3936,3945 **** ExecEvalFieldStore(FieldStoreState *fstate,
TupleDesc tupDesc;
Datum *values;
bool *isnull;
- Datum save_datum;
- bool save_isNull;
ListCell *l1,
! *l2;
tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone);
--- 3886,3894 ----
TupleDesc tupDesc;
Datum *values;
bool *isnull;
ListCell *l1,
! *l2,
! *l3;
tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone);
***************
*** 3980,4006 **** ExecEvalFieldStore(FieldStoreState *fstate,
/* Result is never null */
*isNull = false;
! save_datum = econtext->caseValue_datum;
! save_isNull = econtext->caseValue_isNull;
!
! forboth(l1, fstate->newvals, l2, fstore->fieldnums)
{
ExprState *newval = (ExprState *) lfirst(l1);
AttrNumber fieldnum = lfirst_int(l2);
Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
/*
! * Use the CaseTestExpr mechanism to pass down the old value of the
* field being replaced; this is needed in case the newval is itself a
* FieldStore or ArrayRef that has to obtain and modify the old value.
- * It's safe to reuse the CASE mechanism because there cannot be a
- * CASE between here and where the value would be needed, and a field
- * assignment can't be within a CASE either. (So saving and restoring
- * the caseValue is just paranoia, but let's do it anyway.)
*/
! econtext->caseValue_datum = values[fieldnum - 1];
! econtext->caseValue_isNull = isnull[fieldnum - 1];
values[fieldnum - 1] = ExecEvalExpr(newval,
econtext,
--- 3929,3953 ----
/* Result is never null */
*isNull = false;
! forthree(l1, fstate->newvals, l2, fstore->fieldnums, l3, fstore->oldvalparamids)
{
ExprState *newval = (ExprState *) lfirst(l1);
AttrNumber fieldnum = lfirst_int(l2);
+ int paramid = lfirst_int(l3);
+ ParamExecData *prm;
+
+ enlargeParamExecVals(econtext, paramid);
+ prm = &econtext->ecxt_param_exec_vals[paramid];
Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
/*
! * Use an exec Param to pass down the old value of the
* field being replaced; this is needed in case the newval is itself a
* FieldStore or ArrayRef that has to obtain and modify the old value.
*/
! prm->value = values[fieldnum - 1];
! prm->isnull = isnull[fieldnum - 1];
values[fieldnum - 1] = ExecEvalExpr(newval,
econtext,
***************
*** 4008,4016 **** ExecEvalFieldStore(FieldStoreState *fstate,
NULL);
}
- econtext->caseValue_datum = save_datum;
- econtext->caseValue_isNull = save_isNull;
-
tuple = heap_form_tuple(tupDesc, values, isnull);
pfree(values);
--- 3955,3960 ----
***************
*** 4256,4263 **** ExecInitExpr(Expr *node, PlanState *parent)
state->evalfunc = ExecEvalCoerceToDomainValue;
break;
case T_CaseTestExpr:
! state = (ExprState *) makeNode(ExprState);
! state->evalfunc = ExecEvalCaseTestExpr;
break;
case T_Aggref:
{
--- 4200,4207 ----
state->evalfunc = ExecEvalCoerceToDomainValue;
break;
case T_CaseTestExpr:
! /* planner should've replaced these with Params */
! elog(ERROR, "CaseTestExpr found in executor tree");
break;
case T_Aggref:
{
***************
*** 4563,4568 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4507,4513 ----
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
cstate->arg = ExecInitExpr(caseexpr->arg, parent);
+ cstate->paramid = caseexpr->paramid;
foreach(l, caseexpr->args)
{
CaseWhen *when = (CaseWhen *) lfirst(l);
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 246,260 **** CreateExprContext(EState *estate)
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
econtext->ecxt_param_exec_vals = estate->es_param_exec_vals;
econtext->ecxt_param_list_info = estate->es_param_list_info;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
- econtext->caseValue_datum = (Datum) 0;
- econtext->caseValue_isNull = true;
-
econtext->domainValue_datum = (Datum) 0;
econtext->domainValue_isNull = true;
--- 246,258 ----
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
+ econtext->num_param_exec_vals = estate->es_num_param_exec_vals;
econtext->ecxt_param_exec_vals = estate->es_param_exec_vals;
econtext->ecxt_param_list_info = estate->es_param_list_info;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
econtext->domainValue_datum = (Datum) 0;
econtext->domainValue_isNull = true;
***************
*** 317,331 **** CreateStandaloneExprContext(void)
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
econtext->ecxt_param_exec_vals = NULL;
econtext->ecxt_param_list_info = NULL;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
- econtext->caseValue_datum = (Datum) 0;
- econtext->caseValue_isNull = true;
-
econtext->domainValue_datum = (Datum) 0;
econtext->domainValue_isNull = true;
--- 315,327 ----
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
+ econtext->num_param_exec_vals = 0;
econtext->ecxt_param_exec_vals = NULL;
econtext->ecxt_param_list_info = NULL;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
econtext->domainValue_datum = (Datum) 0;
econtext->domainValue_isNull = true;
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1168,1173 **** _copyArrayRef(ArrayRef *from)
--- 1168,1174 ----
COPY_NODE_FIELD(reflowerindexpr);
COPY_NODE_FIELD(refexpr);
COPY_NODE_FIELD(refassgnexpr);
+ COPY_SCALAR_FIELD(refparamid);
return newnode;
}
***************
*** 1386,1391 **** _copyFieldStore(FieldStore *from)
--- 1387,1393 ----
COPY_NODE_FIELD(arg);
COPY_NODE_FIELD(newvals);
COPY_NODE_FIELD(fieldnums);
+ COPY_NODE_FIELD(oldvalparamids);
COPY_SCALAR_FIELD(resulttype);
return newnode;
***************
*** 1488,1493 **** _copyCaseExpr(CaseExpr *from)
--- 1490,1496 ----
COPY_SCALAR_FIELD(casetype);
COPY_SCALAR_FIELD(casecollid);
COPY_NODE_FIELD(arg);
+ COPY_SCALAR_FIELD(paramid);
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(defresult);
COPY_LOCATION_FIELD(location);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 224,229 **** _equalArrayRef(ArrayRef *a, ArrayRef *b)
--- 224,230 ----
COMPARE_NODE_FIELD(reflowerindexpr);
COMPARE_NODE_FIELD(refexpr);
COMPARE_NODE_FIELD(refassgnexpr);
+ COMPARE_SCALAR_FIELD(refparamid);
return true;
}
***************
*** 435,440 **** _equalFieldStore(FieldStore *a, FieldStore *b)
--- 436,442 ----
COMPARE_NODE_FIELD(arg);
COMPARE_NODE_FIELD(newvals);
COMPARE_NODE_FIELD(fieldnums);
+ COMPARE_NODE_FIELD(oldvalparamids);
COMPARE_SCALAR_FIELD(resulttype);
return true;
***************
*** 543,548 **** _equalCaseExpr(CaseExpr *a, CaseExpr *b)
--- 545,551 ----
COMPARE_SCALAR_FIELD(casetype);
COMPARE_SCALAR_FIELD(casecollid);
COMPARE_NODE_FIELD(arg);
+ COMPARE_SCALAR_FIELD(paramid);
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(defresult);
COMPARE_LOCATION_FIELD(location);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 989,994 **** _outArrayRef(StringInfo str, ArrayRef *node)
--- 989,995 ----
WRITE_NODE_FIELD(reflowerindexpr);
WRITE_NODE_FIELD(refexpr);
WRITE_NODE_FIELD(refassgnexpr);
+ WRITE_INT_FIELD(refparamid);
}
static void
***************
*** 1164,1169 **** _outFieldStore(StringInfo str, FieldStore *node)
--- 1165,1171 ----
WRITE_NODE_FIELD(arg);
WRITE_NODE_FIELD(newvals);
WRITE_NODE_FIELD(fieldnums);
+ WRITE_NODE_FIELD(oldvalparamids);
WRITE_OID_FIELD(resulttype);
}
***************
*** 1236,1241 **** _outCaseExpr(StringInfo str, CaseExpr *node)
--- 1238,1244 ----
WRITE_OID_FIELD(casetype);
WRITE_OID_FIELD(casecollid);
WRITE_NODE_FIELD(arg);
+ WRITE_INT_FIELD(paramid);
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(defresult);
WRITE_LOCATION_FIELD(location);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 520,525 **** _readArrayRef(void)
--- 520,526 ----
READ_NODE_FIELD(reflowerindexpr);
READ_NODE_FIELD(refexpr);
READ_NODE_FIELD(refassgnexpr);
+ READ_INT_FIELD(refparamid);
READ_DONE();
}
***************
*** 757,762 **** _readFieldStore(void)
--- 758,764 ----
READ_NODE_FIELD(arg);
READ_NODE_FIELD(newvals);
READ_NODE_FIELD(fieldnums);
+ READ_NODE_FIELD(oldvalparamids);
READ_OID_FIELD(resulttype);
READ_DONE();
***************
*** 859,864 **** _readCaseExpr(void)
--- 861,867 ----
READ_OID_FIELD(casetype);
READ_OID_FIELD(casecollid);
READ_NODE_FIELD(arg);
+ READ_INT_FIELD(paramid);
READ_NODE_FIELD(args);
READ_NODE_FIELD(defresult);
READ_LOCATION_FIELD(location);
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 3009,3015 **** get_column_info_for_window(PlannerInfo *root, WindowClause *wc, List *tlist,
* calls are converted to positional notation and function default arguments
* get inserted. The fact that constant subexpressions get simplified is a
* side-effect that is useful when the expression will get evaluated more than
! * once. Also, we must fix operator function IDs.
*
* Note: this must not make any damaging changes to the passed-in expression
* tree. (It would actually be okay to apply fix_opfuncids to it, but since
--- 3009,3016 ----
* calls are converted to positional notation and function default arguments
* get inserted. The fact that constant subexpressions get simplified is a
* side-effect that is useful when the expression will get evaluated more than
! * once. Also, we must fix operator function IDs. Also, this converts
! * CaseTestExprs to executor Params.
*
* Note: this must not make any damaging changes to the passed-in expression
* tree. (It would actually be okay to apply fix_opfuncids to it, but since
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 57,62 **** typedef struct
--- 57,63 ----
typedef struct
{
ParamListInfo boundParams;
+ int nExecParams;
PlannerGlobal *glob;
List *active_fns;
Node *case_val;
***************
*** 2103,2108 **** eval_const_expressions(PlannerInfo *root, Node *node)
--- 2104,2110 ----
context.boundParams = NULL;
context.glob = NULL;
}
+ context.nExecParams = 0; /* no exec params assigned */
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
***************
*** 2134,2145 **** estimate_expression_value(PlannerInfo *root, Node *node)
--- 2136,2189 ----
context.boundParams = root->glob->boundParams; /* bound Params */
/* we do not need to mark the plan as depending on inlined functions */
context.glob = NULL;
+ context.nExecParams = 0; /* no exec params assigned */
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
return eval_const_expressions_mutator(node, &context);
}
+ /*
+ * Generate a new PARAM_EXEC Param node, for use in an expression.
+ */
+ static Node *
+ generate_expr_param(eval_const_expressions_context *context, Oid paramtype,
+ int32 paramtypmod, Oid paramcollation, int *paramid)
+ {
+ Param *retval;
+ PlannerParamItem *pitem;
+
+ /*
+ * If this expression is being planned as part of a query, the expression
+ * params are included in the global list of PARAM_EXEC params. If
+ * this is a stand-alone expression, we just keep a counter of how many
+ * we have used.
+ */
+ if (context->glob)
+ *paramid = list_length(context->glob->paramlist);
+ else
+ *paramid = context->nExecParams++;
+
+ retval = makeNode(Param);
+ retval->paramkind = PARAM_EXEC;
+ retval->paramid = *paramid;
+ retval->paramtype = paramtype;
+ retval->paramtypmod = paramtypmod;
+ retval->paramcollid = paramcollation;
+ retval->location = -1;
+
+ if (context->glob)
+ {
+ pitem = makeNode(PlannerParamItem);
+ pitem->item = (Node *) retval;
+ pitem->abslevel = 0; /* XXX */
+
+ context->glob->paramlist = lappend(context->glob->paramlist, pitem);
+ }
+
+ return (Node *) retval;
+ }
+
static Node *
eval_const_expressions_mutator(Node *node,
eval_const_expressions_context *context)
***************
*** 2722,2727 **** eval_const_expressions_mutator(Node *node,
--- 2766,2772 ----
CaseExpr *newcase;
Node *save_case_val;
Node *newarg;
+ int paramid;
List *newargs;
bool const_true_cond;
Node *defresult = NULL;
***************
*** 2731,2745 **** eval_const_expressions_mutator(Node *node,
newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
context);
! /* Set up for contained CaseTestExpr nodes */
save_case_val = context->case_val;
if (newarg && IsA(newarg, Const))
{
context->case_val = newarg;
! newarg = NULL; /* not needed anymore, see comment above */
}
else
! context->case_val = NULL;
/* Simplify the WHEN clauses */
newargs = NIL;
--- 2776,2802 ----
newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
context);
! /*
! * Set up for contained CaseTestExpr nodes. If the case value is
! * a constant, we can simply replace the CaseTestExpr nodes with the
! * constant. Otherwise, create a executor Param to pass down the value
! * at runtime from the CaseExpr. It would be legal to do the simple
! * replacement for any non-volatile expression, but that's unlikely
! * to be a win if the expression is any more complicated than a simple
! * Const.
! */
save_case_val = context->case_val;
if (newarg && IsA(newarg, Const))
{
context->case_val = newarg;
! newarg = NULL; /* not needed anymore */
}
else
! context->case_val = generate_expr_param(context,
! exprType(newarg),
! exprTypmod(newarg),
! exprCollation(newarg),
! ¶mid);
/* Simplify the WHEN clauses */
newargs = NIL;
***************
*** 2813,2818 **** eval_const_expressions_mutator(Node *node,
--- 2870,2876 ----
newcase->casetype = caseexpr->casetype;
newcase->casecollid = caseexpr->casecollid;
newcase->arg = (Expr *) newarg;
+ newcase->paramid = paramid;
newcase->args = newargs;
newcase->defresult = (Expr *) defresult;
newcase->location = caseexpr->location;
***************
*** 2821,2834 **** eval_const_expressions_mutator(Node *node,
if (IsA(node, CaseTestExpr))
{
/*
! * If we know a constant test value for the current CASE construct,
! * substitute it for the placeholder. Else just return the
! * placeholder as-is.
*/
! if (context->case_val)
! return copyObject(context->case_val);
else
! return copyObject(node);
}
if (IsA(node, ArrayExpr))
{
--- 2879,3007 ----
if (IsA(node, CaseTestExpr))
{
/*
! * We should have a constant or a Param for the current CASE construct,
! * substitute it for the placeholder.
! */
! if (!context->case_val)
! elog(ERROR, "unexpected CaseTestExpr outside CASE expression");
! return copyObject(context->case_val);
! }
! if (IsA(node, FieldStore))
! {
! FieldStore *fstore = (FieldStore *) node;
! FieldStore *newfstore;
! List *newvals;
! List *oldvalparamids;
! Node *save_case_val;
! ListCell *lfno,
! *lnv;
!
! save_case_val = context->case_val;
! /* Create a Param to hold the old value for each field */
! newvals = oldvalparamids = NIL;
! forboth(lnv, fstore->newvals, lfno, fstore->fieldnums)
! {
! Node *newval = lfirst(lnv);
! int fieldnum = lfirst_int(lfno);
! Node *oldval;
! int paramid;
! TupleDesc tupdesc;
! Form_pg_attribute attr;
!
! tupdesc = lookup_rowtype_tupdesc(fstore->resulttype, -1);
! if (fieldnum <= 0 || fieldnum > tupdesc->natts)
! elog(ERROR, "invalid attno %d for row type %u", fieldnum,
! fstore->resulttype);
!
! attr = tupdesc->attrs[fieldnum - 1];
!
! /*
! * Replace CaseTestExprs in the new value with the Param which
! * will hold the old value of the field at runtime.
! */
! oldval = generate_expr_param(context, attr->atttypid,
! attr->atttypmod,
! attr->attcollation,
! ¶mid);
! ReleaseTupleDesc(tupdesc);
!
! context->case_val = oldval;
! newval = eval_const_expressions_mutator((Node *) newval, context);
!
! newvals = lappend(newvals, newval);
! oldvalparamids = lappend_int(oldvalparamids, paramid);
! }
! context->case_val = save_case_val;
!
! newfstore = makeNode(FieldStore);
! newfstore->arg = (Expr *) eval_const_expressions_mutator((Node *) fstore->arg,
! context);
! newfstore->newvals = newvals;
! newfstore->fieldnums = fstore->fieldnums;
! newfstore->oldvalparamids = oldvalparamids;
! newfstore->resulttype = fstore->resulttype;
!
! return (Node *) newfstore;
! }
! if (IsA(node, ArrayRef))
! {
! ArrayRef *aref = (ArrayRef *) node;
! ArrayRef *newaref;
! int paramid;
! Node *save_case_val;
! Node *oldval;
! Node *newrefassgnexpr;
!
! /*
! * We might have a nested-assignment situation, in which the
! * refassgnexpr is itself a FieldStore or ArrayRef that needs to
! * obtain and modify the previous value of the array element or slice
! * being replaced. If so, we have to extract that value from the
! * array and pass it down via a executor Param. Parse analysis has
! * used CaseTestExpr as a placeholder for it, replace it with a Param
! * now.
! *
! * Since fetching the old element might be a nontrivial expense, do it
! * only if the argument appears to actually need it.
*/
! if (aref->refassgnexpr && (IsA(aref->refassgnexpr, FieldStore) ||
! IsA(aref->refassgnexpr, ArrayRef)))
! {
! /*
! * Replace CaseTestExprs in the new value with the Param which
! * will hold the old value of the field at runtime.
! */
! oldval = generate_expr_param(context, aref->refelemtype,
! aref->reftypmod,
! aref->refcollid,
! ¶mid);
! }
else
! {
! oldval = NULL;
! paramid = -1;
! }
!
! save_case_val = context->case_val;
! context->case_val = oldval;
! newrefassgnexpr = eval_const_expressions_mutator((Node *) aref->refassgnexpr, context);
! context->case_val = save_case_val;
!
! newaref = makeNode(ArrayRef);
! newaref->refarraytype = aref->refarraytype;
! newaref->refelemtype = aref->refelemtype;
! newaref->reftypmod = aref->reftypmod;
! newaref->refcollid = aref->refcollid;
! newaref->refupperindexpr = (List *) eval_const_expressions_mutator((Node *) aref->refupperindexpr,
! context);
! newaref->reflowerindexpr = (List *) eval_const_expressions_mutator((Node *) aref->reflowerindexpr,
! context);
! newaref->refexpr = (Expr *) eval_const_expressions_mutator((Node *) aref->refexpr,
! context);
! newaref->refassgnexpr = (Expr *) newrefassgnexpr;
! newaref->refparamid = paramid;
!
! return (Node *) newaref;
}
if (IsA(node, ArrayExpr))
{
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 122,127 **** typedef struct ExprContext
--- 122,128 ----
MemoryContext ecxt_per_tuple_memory;
/* Values to substitute for Param nodes in expression */
+ int num_param_exec_vals;
ParamExecData *ecxt_param_exec_vals; /* for PARAM_EXEC params */
ParamListInfo ecxt_param_list_info; /* for other param types */
***************
*** 132,141 **** typedef struct ExprContext
Datum *ecxt_aggvalues; /* precomputed values for aggs/windowfuncs */
bool *ecxt_aggnulls; /* null flags for aggs/windowfuncs */
- /* Value to substitute for CaseTestExpr nodes in expression */
- Datum caseValue_datum;
- bool caseValue_isNull;
-
/* Value to substitute for CoerceToDomainValue nodes in expression */
Datum domainValue_datum;
bool domainValue_isNull;
--- 133,138 ----
***************
*** 358,363 **** typedef struct EState
--- 355,361 ----
/* Parameter info: */
ParamListInfo es_param_list_info; /* values of external params */
+ int es_num_param_exec_vals;
ParamExecData *es_param_exec_vals; /* values of internal params */
/* Other working state: */
***************
*** 812,817 **** typedef struct CaseExprState
--- 810,816 ----
{
ExprState xprstate;
ExprState *arg; /* implicit equality comparison argument */
+ int paramid; /* exec param slot to hold the argument */
List *args; /* the arguments (list of WHEN clauses) */
ExprState *defresult; /* the default result (ELSE clause) */
} CaseExprState;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 295,300 **** typedef struct ArrayRef
--- 295,302 ----
* value */
Expr *refassgnexpr; /* expression for the source value, or NULL if
* fetch */
+ int refparamid; /* exec param to hold old value during
+ * refassgnexpr execution */
} ArrayRef;
/*
***************
*** 639,644 **** typedef struct FieldStore
--- 641,647 ----
Expr *arg; /* input tuple value */
List *newvals; /* new value(s) for field(s) */
List *fieldnums; /* integer list of field attnums */
+ List *oldvalparamids; /* exec params to hold old values of fields */
Oid resulttype; /* type of result (same as type of arg) */
/* Like RowExpr, we deliberately omit a typmod and collation here */
} FieldStore;
***************
*** 767,772 **** typedef struct CollateExpr
--- 770,778 ----
* evaluated only once. Note that after parse analysis, the condition
* expressions always yield boolean.
*
+ * The planner will replace CaseTestExpr with a exec Param node, and set
+ * paramid.
+ *
* Note: we can test whether a CaseExpr has been through parse analysis
* yet by checking whether casetype is InvalidOid or not.
*----------
***************
*** 777,782 **** typedef struct CaseExpr
--- 783,789 ----
Oid casetype; /* type of expression result */
Oid casecollid; /* OID of collation, or InvalidOid if none */
Expr *arg; /* implicit equality comparison argument */
+ int paramid;
List *args; /* the arguments (list of WHEN clauses) */
Expr *defresult; /* the default result (ELSE clause) */
int location; /* token location, or -1 if unknown */
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 5459,5467 **** exec_simple_check_node(Node *node)
return TRUE;
}
- case T_CaseTestExpr:
- return TRUE;
-
case T_ArrayExpr:
{
ArrayExpr *expr = (ArrayExpr *) node;
--- 5459,5464 ----
On 31.05.2011 19:10, Heikki Linnakangas wrote:
For index expressions, we could use a function similar to
ChangeVarNodes(), that shifts all the paramids in the already-planned
expression, preparing it for inclusion within the enclosing plan. I'm a
bit worried that that might screw up the logic used to compare if an
expression matches the index expression, though; the param ids in the
two expressions won't match.
Yeah, the expression comparison logic gets all confused by this :-(. I
couldn't figure out a way to make it work without making the comparison
a whole lot more complicated than it is today. I'm going back to my
original thoughts of a new kind of node to replace CaseTestExpr, which
allows referencing values from upper levels in the expression tree.
So, here's a WIP patch using that approach. I've replaced CaseTestExpr
with ExpressionParam. ExpressionParam has a levelsup field, which is
similar to varlevelsup in Var. With levelsup == 0, ExpressionParam works
just like CaseTestExpr did. With levelsup == 1, it refers to the value
from above the enclosing CaseExpr (or any other node that uses these
ExpressionParams/CaseTestExprs).
The complicated part is to ensure that levelsup is always set correctly.
At parse time, levelsup is always set to 0, as the syntax doesn't allow
referencing upper levels directly. When an SQL function is inlined, any
ExpressionParams in the expressions that are substituted for Params need
to have their levelsup adjusted, so that it still refers to the right
value if there's CASE expressions in the inlined function. Also, when an
ExpressionParam is replaced with a Const, the levelsup fields of any
other ExpressionParams within the CaseExpr referring to higher levels
need to have their levelsup decremented to account for the fact that the
CaseExpr doesn't push the expression parameter anymore.
At execution time, the expression parameters form a stack. We've always
done the save-restore logic, but the stack is now represented explicitly
as a List in ExprContext. When an ExpressionParam is evaluated, the nth
element is fetched from the stack, corresponding to levelsup.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Attachments:
replace-casetestexpr-with-expressionparams-2.patchtext/x-diff; name=replace-casetestexpr-with-expressionparams-2.patchDownload
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 61,67 ****
static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
- static bool isAssignmentIndirectionExpr(ExprState *exprstate);
static Datum ExecEvalAggref(AggrefExprState *aggref,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
--- 61,66 ----
***************
*** 82,87 **** static Datum ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
--- 81,88 ----
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+ static Datum ExecEvalExpressionParam(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
static void init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
MemoryContext fcacheCxt, bool needDescForSets);
static void ShutdownFuncExpr(Datum arg);
***************
*** 122,130 **** static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
- static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
- ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalArray(ArrayExprState *astate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
--- 123,128 ----
***************
*** 352,394 **** ExecEvalArrayRef(ArrayRefExprState *astate,
if (isAssignment)
{
Datum sourceData;
! Datum save_datum;
! bool save_isNull;
/*
* We might have a nested-assignment situation, in which the
* refassgnexpr is itself a FieldStore or ArrayRef that needs to
* obtain and modify the previous value of the array element or slice
* being replaced. If so, we have to extract that value from the
! * array and pass it down via the econtext's caseValue. It's safe to
! * reuse the CASE mechanism because there cannot be a CASE between
! * here and where the value would be needed, and an array assignment
! * can't be within a CASE either. (So saving and restoring the
! * caseValue is just paranoia, but let's do it anyway.)
! *
! * Since fetching the old element might be a nontrivial expense, do it
! * only if the argument appears to actually need it.
*/
! save_datum = econtext->caseValue_datum;
! save_isNull = econtext->caseValue_isNull;
!
! if (isAssignmentIndirectionExpr(astate->refassgnexpr))
{
if (*isNull)
{
/* whole array is null, so any element or slice is too */
! econtext->caseValue_datum = (Datum) 0;
! econtext->caseValue_isNull = true;
}
else if (lIndex == NULL)
{
! econtext->caseValue_datum = array_ref(array_source, i,
! upper.indx,
! astate->refattrlength,
! astate->refelemlength,
! astate->refelembyval,
! astate->refelemalign,
! &econtext->caseValue_isNull);
}
else
{
--- 350,382 ----
if (isAssignment)
{
Datum sourceData;
! ParamExecData prm;
/*
* We might have a nested-assignment situation, in which the
* refassgnexpr is itself a FieldStore or ArrayRef that needs to
* obtain and modify the previous value of the array element or slice
* being replaced. If so, we have to extract that value from the
! * array and pass it down via an expression param.
*/
! if (arrayRef->refuseparam)
{
+ prm.execPlan = NULL; /* not used */
if (*isNull)
{
/* whole array is null, so any element or slice is too */
! prm.value = (Datum) 0;
! prm.isnull = true;
}
else if (lIndex == NULL)
{
! prm.value = array_ref(array_source, i,
! upper.indx,
! astate->refattrlength,
! astate->refelemlength,
! astate->refelembyval,
! astate->refelemalign,
! &prm.isnull);
}
else
{
***************
*** 398,412 **** ExecEvalArrayRef(ArrayRefExprState *astate,
astate->refelemlength,
astate->refelembyval,
astate->refelemalign);
! econtext->caseValue_datum = PointerGetDatum(resultArray);
! econtext->caseValue_isNull = false;
}
! }
! else
! {
! /* argument shouldn't need caseValue, but for safety set it null */
! econtext->caseValue_datum = (Datum) 0;
! econtext->caseValue_isNull = true;
}
/*
--- 386,395 ----
astate->refelemlength,
astate->refelembyval,
astate->refelemalign);
! prm.value = PointerGetDatum(resultArray);
! prm.isnull = false;
}
! econtext->expr_params = lcons(&prm, econtext->expr_params);
}
/*
***************
*** 417,424 **** ExecEvalArrayRef(ArrayRefExprState *astate,
&eisnull,
NULL);
! econtext->caseValue_datum = save_datum;
! econtext->caseValue_isNull = save_isNull;
/*
* For an assignment to a fixed-length array type, both the original
--- 400,408 ----
&eisnull,
NULL);
! /* pop the expression param stack if we pushed */
! if (arrayRef->refuseparam)
! econtext->expr_params = list_delete_first(econtext->expr_params);
/*
* For an assignment to a fixed-length array type, both the original
***************
*** 481,515 **** ExecEvalArrayRef(ArrayRefExprState *astate,
}
}
- /*
- * Helper for ExecEvalArrayRef: is expr a nested FieldStore or ArrayRef
- * that might need the old element value passed down?
- *
- * (We could use this in ExecEvalFieldStore too, but in that case passing
- * the old value is so cheap there's no need.)
- */
- static bool
- isAssignmentIndirectionExpr(ExprState *exprstate)
- {
- if (exprstate == NULL)
- return false; /* just paranoia */
- if (IsA(exprstate, FieldStoreState))
- {
- FieldStore *fstore = (FieldStore *) exprstate->expr;
-
- if (fstore->arg && IsA(fstore->arg, CaseTestExpr))
- return true;
- }
- else if (IsA(exprstate, ArrayRefExprState))
- {
- ArrayRef *arrayRef = (ArrayRef *) exprstate->expr;
-
- if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr))
- return true;
- }
- return false;
- }
-
/* ----------------------------------------------------------------
* ExecEvalAggref
*
--- 465,470 ----
***************
*** 1046,1051 **** ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext,
--- 1001,1032 ----
return (Datum) 0; /* keep compiler quiet */
}
+ /* ----------------------------------------------------------------
+ * ExecEvalExpressionParam
+ *
+ * Returns the value of an expression parameter.
+ * ----------------------------------------------------------------
+ */
+ static Datum
+ ExecEvalExpressionParam(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+ {
+ ExpressionParam *eparam = (ExpressionParam *) exprstate->expr;
+ ParamExecData *prm;
+
+ if (isDone)
+ *isDone = ExprSingleResult;
+
+ /* Expression params are stored in a stack, in econtext->expr_params */
+ if (eparam->levelsup >= list_length(econtext->expr_params))
+ elog(ERROR, "invalid expression parameter reference (%d levels up, while stack is only %d elements deep)",
+ eparam->levelsup, list_length(econtext->expr_params));
+ prm = list_nth(econtext->expr_params, eparam->levelsup);
+
+ *isNull = prm->isnull;
+ return prm->value;
+ }
+
/* ----------------------------------------------------------------
* ExecEvalOper / ExecEvalFunc support routines
***************
*** 2769,2795 **** ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
{
List *clauses = caseExpr->args;
ListCell *clause;
! Datum save_datum;
! bool save_isNull;
if (isDone)
*isDone = ExprSingleResult;
/*
* If there's a test expression, we have to evaluate it and save the value
! * where the CaseTestExpr placeholders can find it. We must save and
! * restore prior setting of econtext's caseValue fields, in case this node
! * is itself within a larger CASE.
*/
- save_datum = econtext->caseValue_datum;
- save_isNull = econtext->caseValue_isNull;
-
if (caseExpr->arg)
{
! econtext->caseValue_datum = ExecEvalExpr(caseExpr->arg,
! econtext,
! &econtext->caseValue_isNull,
! NULL);
}
/*
--- 2750,2772 ----
{
List *clauses = caseExpr->args;
ListCell *clause;
! ParamExecData prm;
if (isDone)
*isDone = ExprSingleResult;
/*
* If there's a test expression, we have to evaluate it and save the value
! * in the right Param slot.
*/
if (caseExpr->arg)
{
! prm.execPlan = NULL; /* not used */
! prm.value = ExecEvalExpr(caseExpr->arg,
! econtext,
! &prm.isnull,
! NULL);
! econtext->expr_params = lcons(&prm, econtext->expr_params);
}
/*
***************
*** 2814,2821 **** ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
*/
if (DatumGetBool(clause_value) && !*isNull)
{
! econtext->caseValue_datum = save_datum;
! econtext->caseValue_isNull = save_isNull;
return ExecEvalExpr(wclause->result,
econtext,
isNull,
--- 2791,2798 ----
*/
if (DatumGetBool(clause_value) && !*isNull)
{
! if (caseExpr->arg)
! econtext->expr_params = list_delete_first(econtext->expr_params);
return ExecEvalExpr(wclause->result,
econtext,
isNull,
***************
*** 2823,2830 **** ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
}
}
! econtext->caseValue_datum = save_datum;
! econtext->caseValue_isNull = save_isNull;
if (caseExpr->defresult)
{
--- 2800,2808 ----
}
}
! /* pop the expression param stack if we pushed a value */
! if (caseExpr->arg)
! econtext->expr_params = list_delete_first(econtext->expr_params);
if (caseExpr->defresult)
{
***************
*** 2838,2859 **** ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
return (Datum) 0;
}
- /*
- * ExecEvalCaseTestExpr
- *
- * Return the value stored by CASE.
- */
- static Datum
- ExecEvalCaseTestExpr(ExprState *exprstate,
- ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone)
- {
- if (isDone)
- *isDone = ExprSingleResult;
- *isNull = econtext->caseValue_isNull;
- return econtext->caseValue_datum;
- }
-
/* ----------------------------------------------------------------
* ExecEvalArray - ARRAY[] expressions
* ----------------------------------------------------------------
--- 2816,2821 ----
***************
*** 3936,3945 **** ExecEvalFieldStore(FieldStoreState *fstate,
TupleDesc tupDesc;
Datum *values;
bool *isnull;
- Datum save_datum;
- bool save_isNull;
ListCell *l1,
*l2;
tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone);
--- 3898,3906 ----
TupleDesc tupDesc;
Datum *values;
bool *isnull;
ListCell *l1,
*l2;
+ ParamExecData prm;
tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone);
***************
*** 3980,3987 **** ExecEvalFieldStore(FieldStoreState *fstate,
/* Result is never null */
*isNull = false;
! save_datum = econtext->caseValue_datum;
! save_isNull = econtext->caseValue_isNull;
forboth(l1, fstate->newvals, l2, fstore->fieldnums)
{
--- 3941,3947 ----
/* Result is never null */
*isNull = false;
! econtext->expr_params = lcons(&prm, econtext->expr_params);
forboth(l1, fstate->newvals, l2, fstore->fieldnums)
{
***************
*** 3991,4006 **** ExecEvalFieldStore(FieldStoreState *fstate,
Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
/*
! * Use the CaseTestExpr mechanism to pass down the old value of the
* field being replaced; this is needed in case the newval is itself a
* FieldStore or ArrayRef that has to obtain and modify the old value.
- * It's safe to reuse the CASE mechanism because there cannot be a
- * CASE between here and where the value would be needed, and a field
- * assignment can't be within a CASE either. (So saving and restoring
- * the caseValue is just paranoia, but let's do it anyway.)
*/
! econtext->caseValue_datum = values[fieldnum - 1];
! econtext->caseValue_isNull = isnull[fieldnum - 1];
values[fieldnum - 1] = ExecEvalExpr(newval,
econtext,
--- 3951,3963 ----
Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
/*
! * Use an exec Param to pass down the old value of the
* field being replaced; this is needed in case the newval is itself a
* FieldStore or ArrayRef that has to obtain and modify the old value.
*/
! prm.execPlan = NULL; /* not used */
! prm.value = values[fieldnum - 1];
! prm.isnull = isnull[fieldnum - 1];
values[fieldnum - 1] = ExecEvalExpr(newval,
econtext,
***************
*** 4008,4015 **** ExecEvalFieldStore(FieldStoreState *fstate,
NULL);
}
! econtext->caseValue_datum = save_datum;
! econtext->caseValue_isNull = save_isNull;
tuple = heap_form_tuple(tupDesc, values, isnull);
--- 3965,3971 ----
NULL);
}
! econtext->expr_params = list_delete_first(econtext->expr_params);
tuple = heap_form_tuple(tupDesc, values, isnull);
***************
*** 4251,4263 **** ExecInitExpr(Expr *node, PlanState *parent)
break;
}
break;
! case T_CoerceToDomainValue:
state = (ExprState *) makeNode(ExprState);
! state->evalfunc = ExecEvalCoerceToDomainValue;
break;
! case T_CaseTestExpr:
state = (ExprState *) makeNode(ExprState);
! state->evalfunc = ExecEvalCaseTestExpr;
break;
case T_Aggref:
{
--- 4207,4219 ----
break;
}
break;
! case T_ExpressionParam:
state = (ExprState *) makeNode(ExprState);
! state->evalfunc = ExecEvalExpressionParam;
break;
! case T_CoerceToDomainValue:
state = (ExprState *) makeNode(ExprState);
! state->evalfunc = ExecEvalCoerceToDomainValue;
break;
case T_Aggref:
{
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 252,260 **** CreateExprContext(EState *estate)
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
- econtext->caseValue_datum = (Datum) 0;
- econtext->caseValue_isNull = true;
-
econtext->domainValue_datum = (Datum) 0;
econtext->domainValue_isNull = true;
--- 252,257 ----
***************
*** 323,331 **** CreateStandaloneExprContext(void)
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
- econtext->caseValue_datum = (Datum) 0;
- econtext->caseValue_isNull = true;
-
econtext->domainValue_datum = (Datum) 0;
econtext->domainValue_isNull = true;
--- 320,325 ----
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1168,1173 **** _copyArrayRef(ArrayRef *from)
--- 1168,1174 ----
COPY_NODE_FIELD(reflowerindexpr);
COPY_NODE_FIELD(refexpr);
COPY_NODE_FIELD(refassgnexpr);
+ COPY_SCALAR_FIELD(refuseparam);
return newnode;
}
***************
*** 1386,1391 **** _copyFieldStore(FieldStore *from)
--- 1387,1393 ----
COPY_NODE_FIELD(arg);
COPY_NODE_FIELD(newvals);
COPY_NODE_FIELD(fieldnums);
+ COPY_SCALAR_FIELD(useparam);
COPY_SCALAR_FIELD(resulttype);
return newnode;
***************
*** 1511,1526 **** _copyCaseWhen(CaseWhen *from)
}
/*
! * _copyCaseTestExpr
*/
! static CaseTestExpr *
! _copyCaseTestExpr(CaseTestExpr *from)
{
! CaseTestExpr *newnode = makeNode(CaseTestExpr);
COPY_SCALAR_FIELD(typeId);
COPY_SCALAR_FIELD(typeMod);
COPY_SCALAR_FIELD(collation);
return newnode;
}
--- 1513,1530 ----
}
/*
! * _copyExpressionParam
*/
! static ExpressionParam *
! _copyExpressionParam(ExpressionParam *from)
{
! ExpressionParam *newnode = makeNode(ExpressionParam);
+ COPY_SCALAR_FIELD(levelsup);
COPY_SCALAR_FIELD(typeId);
COPY_SCALAR_FIELD(typeMod);
COPY_SCALAR_FIELD(collation);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
***************
*** 4042,4049 **** copyObject(void *from)
case T_CaseWhen:
retval = _copyCaseWhen(from);
break;
! case T_CaseTestExpr:
! retval = _copyCaseTestExpr(from);
break;
case T_ArrayExpr:
retval = _copyArrayExpr(from);
--- 4046,4053 ----
case T_CaseWhen:
retval = _copyCaseWhen(from);
break;
! case T_ExpressionParam:
! retval = _copyExpressionParam(from);
break;
case T_ArrayExpr:
retval = _copyArrayExpr(from);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 224,229 **** _equalArrayRef(ArrayRef *a, ArrayRef *b)
--- 224,230 ----
COMPARE_NODE_FIELD(reflowerindexpr);
COMPARE_NODE_FIELD(refexpr);
COMPARE_NODE_FIELD(refassgnexpr);
+ COMPARE_SCALAR_FIELD(refuseparam);
return true;
}
***************
*** 435,440 **** _equalFieldStore(FieldStore *a, FieldStore *b)
--- 436,442 ----
COMPARE_NODE_FIELD(arg);
COMPARE_NODE_FIELD(newvals);
COMPARE_NODE_FIELD(fieldnums);
+ COMPARE_SCALAR_FIELD(useparam);
COMPARE_SCALAR_FIELD(resulttype);
return true;
***************
*** 561,571 **** _equalCaseWhen(CaseWhen *a, CaseWhen *b)
}
static bool
! _equalCaseTestExpr(CaseTestExpr *a, CaseTestExpr *b)
{
COMPARE_SCALAR_FIELD(typeId);
COMPARE_SCALAR_FIELD(typeMod);
COMPARE_SCALAR_FIELD(collation);
return true;
}
--- 563,575 ----
}
static bool
! _equalExpressionParam(ExpressionParam *a, ExpressionParam *b)
{
+ COMPARE_SCALAR_FIELD(levelsup);
COMPARE_SCALAR_FIELD(typeId);
COMPARE_SCALAR_FIELD(typeMod);
COMPARE_SCALAR_FIELD(collation);
+ COMPARE_LOCATION_FIELD(location);
return true;
}
***************
*** 2608,2615 **** equal(void *a, void *b)
case T_CaseWhen:
retval = _equalCaseWhen(a, b);
break;
! case T_CaseTestExpr:
! retval = _equalCaseTestExpr(a, b);
break;
case T_ArrayExpr:
retval = _equalArrayExpr(a, b);
--- 2612,2619 ----
case T_CaseWhen:
retval = _equalCaseWhen(a, b);
break;
! case T_ExpressionParam:
! retval = _equalExpressionParam(a, b);
break;
case T_ArrayExpr:
retval = _equalArrayExpr(a, b);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 180,187 **** exprType(Node *expr)
case T_CaseExpr:
type = ((CaseExpr *) expr)->casetype;
break;
! case T_CaseTestExpr:
! type = ((CaseTestExpr *) expr)->typeId;
break;
case T_ArrayExpr:
type = ((ArrayExpr *) expr)->array_typeid;
--- 180,187 ----
case T_CaseExpr:
type = ((CaseExpr *) expr)->casetype;
break;
! case T_ExpressionParam:
! type = ((ExpressionParam *) expr)->typeId;
break;
case T_ArrayExpr:
type = ((ArrayExpr *) expr)->array_typeid;
***************
*** 365,372 **** exprTypmod(Node *expr)
return typmod;
}
break;
! case T_CaseTestExpr:
! return ((CaseTestExpr *) expr)->typeMod;
case T_ArrayExpr:
{
/*
--- 365,372 ----
return typmod;
}
break;
! case T_ExpressionParam:
! return ((ExpressionParam *) expr)->typeMod;
case T_ArrayExpr:
{
/*
***************
*** 756,763 **** exprCollation(Node *expr)
case T_CaseExpr:
coll = ((CaseExpr *) expr)->casecollid;
break;
! case T_CaseTestExpr:
! coll = ((CaseTestExpr *) expr)->collation;
break;
case T_ArrayExpr:
coll = ((ArrayExpr *) expr)->array_collid;
--- 756,763 ----
case T_CaseExpr:
coll = ((CaseExpr *) expr)->casecollid;
break;
! case T_ExpressionParam:
! coll = ((ExpressionParam *) expr)->collation;
break;
case T_ArrayExpr:
coll = ((ArrayExpr *) expr)->array_collid;
***************
*** 1525,1531 **** expression_tree_walker(Node *node,
case T_Const:
case T_Param:
case T_CoerceToDomainValue:
! case T_CaseTestExpr:
case T_SetToDefault:
case T_CurrentOfExpr:
case T_RangeTblRef:
--- 1525,1531 ----
case T_Const:
case T_Param:
case T_CoerceToDomainValue:
! case T_ExpressionParam:
case T_SetToDefault:
case T_CurrentOfExpr:
case T_RangeTblRef:
***************
*** 2040,2046 **** expression_tree_mutator(Node *node,
break;
case T_Param:
case T_CoerceToDomainValue:
! case T_CaseTestExpr:
case T_SetToDefault:
case T_CurrentOfExpr:
case T_RangeTblRef:
--- 2040,2046 ----
break;
case T_Param:
case T_CoerceToDomainValue:
! case T_ExpressionParam:
case T_SetToDefault:
case T_CurrentOfExpr:
case T_RangeTblRef:
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 989,994 **** _outArrayRef(StringInfo str, ArrayRef *node)
--- 989,995 ----
WRITE_NODE_FIELD(reflowerindexpr);
WRITE_NODE_FIELD(refexpr);
WRITE_NODE_FIELD(refassgnexpr);
+ WRITE_BOOL_FIELD(refuseparam);
}
static void
***************
*** 1164,1169 **** _outFieldStore(StringInfo str, FieldStore *node)
--- 1165,1171 ----
WRITE_NODE_FIELD(arg);
WRITE_NODE_FIELD(newvals);
WRITE_NODE_FIELD(fieldnums);
+ WRITE_BOOL_FIELD(useparam);
WRITE_OID_FIELD(resulttype);
}
***************
*** 1252,1264 **** _outCaseWhen(StringInfo str, CaseWhen *node)
}
static void
! _outCaseTestExpr(StringInfo str, CaseTestExpr *node)
{
! WRITE_NODE_TYPE("CASETESTEXPR");
WRITE_OID_FIELD(typeId);
WRITE_INT_FIELD(typeMod);
WRITE_OID_FIELD(collation);
}
static void
--- 1254,1268 ----
}
static void
! _outExpressionParam(StringInfo str, ExpressionParam *node)
{
! WRITE_NODE_TYPE("EXPRESSIONPARAM");
+ WRITE_INT_FIELD(levelsup);
WRITE_OID_FIELD(typeId);
WRITE_INT_FIELD(typeMod);
WRITE_OID_FIELD(collation);
+ WRITE_LOCATION_FIELD(location);
}
static void
***************
*** 2878,2885 **** _outNode(StringInfo str, void *obj)
case T_CaseWhen:
_outCaseWhen(str, obj);
break;
! case T_CaseTestExpr:
! _outCaseTestExpr(str, obj);
break;
case T_ArrayExpr:
_outArrayExpr(str, obj);
--- 2882,2889 ----
case T_CaseWhen:
_outCaseWhen(str, obj);
break;
! case T_ExpressionParam:
! _outExpressionParam(str, obj);
break;
case T_ArrayExpr:
_outArrayExpr(str, obj);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 520,525 **** _readArrayRef(void)
--- 520,526 ----
READ_NODE_FIELD(reflowerindexpr);
READ_NODE_FIELD(refexpr);
READ_NODE_FIELD(refassgnexpr);
+ READ_BOOL_FIELD(refuseparam);
READ_DONE();
}
***************
*** 757,762 **** _readFieldStore(void)
--- 758,764 ----
READ_NODE_FIELD(arg);
READ_NODE_FIELD(newvals);
READ_NODE_FIELD(fieldnums);
+ READ_BOOL_FIELD(useparam);
READ_OID_FIELD(resulttype);
READ_DONE();
***************
*** 882,897 **** _readCaseWhen(void)
}
/*
! * _readCaseTestExpr
*/
! static CaseTestExpr *
! _readCaseTestExpr(void)
{
! READ_LOCALS(CaseTestExpr);
READ_OID_FIELD(typeId);
READ_INT_FIELD(typeMod);
READ_OID_FIELD(collation);
READ_DONE();
}
--- 884,901 ----
}
/*
! * _readExpressionParam
*/
! static ExpressionParam *
! _readExpressionParam(void)
{
! READ_LOCALS(ExpressionParam);
+ READ_INT_FIELD(levelsup);
READ_OID_FIELD(typeId);
READ_INT_FIELD(typeMod);
READ_OID_FIELD(collation);
+ READ_LOCATION_FIELD(location);
READ_DONE();
}
***************
*** 1314,1321 **** parseNodeString(void)
return_value = _readCaseExpr();
else if (MATCH("WHEN", 4))
return_value = _readCaseWhen();
! else if (MATCH("CASETESTEXPR", 12))
! return_value = _readCaseTestExpr();
else if (MATCH("ARRAY", 5))
return_value = _readArrayExpr();
else if (MATCH("ROW", 3))
--- 1318,1325 ----
return_value = _readCaseExpr();
else if (MATCH("WHEN", 4))
return_value = _readCaseWhen();
! else if (MATCH("EXPRESSIONPARAM", 15))
! return_value = _readExpressionParam();
else if (MATCH("ARRAY", 5))
return_value = _readArrayExpr();
else if (MATCH("ROW", 3))
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 59,73 **** typedef struct
ParamListInfo boundParams;
PlannerGlobal *glob;
List *active_fns;
- Node *case_val;
bool estimate;
} eval_const_expressions_context;
typedef struct
{
int nargs;
List *args;
int *usecounts;
} substitute_actual_parameters_context;
typedef struct
--- 59,79 ----
ParamListInfo boundParams;
PlannerGlobal *glob;
List *active_fns;
bool estimate;
} eval_const_expressions_context;
typedef struct
{
+ Node *replacement;
+ int replacement_level;
+ } replace_expression_param_context;
+
+ typedef struct
+ {
int nargs;
List *args;
int *usecounts;
+ int expr_param_depth;
} substitute_actual_parameters_context;
typedef struct
***************
*** 129,138 **** static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid,
--- 135,150 ----
Oid input_collid, List *args,
HeapTuple func_tuple,
eval_const_expressions_context *context);
+ static int expression_params_used(Node *node);
static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
int *usecounts);
static Node *substitute_actual_parameters_mutator(Node *node,
substitute_actual_parameters_context *context);
+ static Node *adjust_expression_params(Node *expr, int offset);
+ static Node *adjust_expression_params_mutator(Node *node, int *offset);
+ static Node *replace_expression_param(Node *expr, Node *replacement);
+ static Node *replace_expression_param_mutator(Node *node,
+ replace_expression_param_context *context);
static void sql_inline_error_callback(void *arg);
static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
***************
*** 2104,2110 **** eval_const_expressions(PlannerInfo *root, Node *node)
context.glob = NULL;
}
context.active_fns = NIL; /* nothing being recursively simplified */
- context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
return eval_const_expressions_mutator(node, &context);
}
--- 2116,2121 ----
***************
*** 2135,2141 **** estimate_expression_value(PlannerInfo *root, Node *node)
/* we do not need to mark the plan as depending on inlined functions */
context.glob = NULL;
context.active_fns = NIL; /* nothing being recursively simplified */
- context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
return eval_const_expressions_mutator(node, &context);
}
--- 2146,2151 ----
***************
*** 2700,2706 **** eval_const_expressions_mutator(Node *node,
* CASE to the default result (ELSE result).
*
* If we have a simple-form CASE with constant test expression,
! * we substitute the constant value for contained CaseTestExpr
* placeholder nodes, so that we have the opportunity to reduce
* constant test conditions. For example this allows
* CASE 0 WHEN 0 THEN 1 ELSE 1/0 END
--- 2710,2716 ----
* CASE to the default result (ELSE result).
*
* If we have a simple-form CASE with constant test expression,
! * we substitute the constant value for contained ExpressionParam
* placeholder nodes, so that we have the opportunity to reduce
* constant test conditions. For example this allows
* CASE 0 WHEN 0 THEN 1 ELSE 1/0 END
***************
*** 2709,2750 **** eval_const_expressions_mutator(Node *node,
* include it in the resulting CASE; for example
* CASE 0 WHEN x THEN y ELSE z END
* is transformed by the parser to
! * CASE 0 WHEN CaseTestExpr = x THEN y ELSE z END
* which we can simplify to
* CASE WHEN 0 = x THEN y ELSE z END
* It is not necessary for the executor to evaluate the "arg"
* expression when executing the CASE, since any contained
! * CaseTestExprs that might have referred to it will have been
* replaced by the constant.
*----------
*/
CaseExpr *caseexpr = (CaseExpr *) node;
CaseExpr *newcase;
- Node *save_case_val;
Node *newarg;
List *newargs;
bool const_true_cond;
Node *defresult = NULL;
ListCell *arg;
/* Simplify the test expression, if any */
newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
context);
! /* Set up for contained CaseTestExpr nodes */
! save_case_val = context->case_val;
! if (newarg && IsA(newarg, Const))
{
! context->case_val = newarg;
! newarg = NULL; /* not needed anymore, see comment above */
}
- else
- context->case_val = NULL;
/* Simplify the WHEN clauses */
newargs = NIL;
const_true_cond = false;
! foreach(arg, caseexpr->args)
{
CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
Node *casecond;
--- 2719,2776 ----
* include it in the resulting CASE; for example
* CASE 0 WHEN x THEN y ELSE z END
* is transformed by the parser to
! * CASE 0 WHEN ExpressionParam = x THEN y ELSE z END
* which we can simplify to
* CASE WHEN 0 = x THEN y ELSE z END
* It is not necessary for the executor to evaluate the "arg"
* expression when executing the CASE, since any contained
! * ExpressionParams that might have referred to it will have been
* replaced by the constant.
*----------
*/
CaseExpr *caseexpr = (CaseExpr *) node;
CaseExpr *newcase;
Node *newarg;
List *newargs;
bool const_true_cond;
Node *defresult = NULL;
ListCell *arg;
+ List *args;
/* Simplify the test expression, if any */
newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
context);
! /*
! * Set up for contained ExpressionParam nodes. If the case value is
! * a constant, we can simply replace the ExpressionParam nodes with the
! * constant. Otherwise, create an expression param to pass down the
! * value at runtime from the CaseExpr. It would be legal to do the
! * simple replacement for any non-volatile expression, but that's
! * unlikely to be a win if the expression is any more complicated than
! * a simple Const.
! *
! * Note: These conditions must match those in expression_params_used()!
! */
! args = caseexpr->args;
! if (newarg)
{
! if (IsA(newarg, Const))
! {
! /*
! * Replace any ExpressionParams referring to this with the
! * constant. This also adjusts the levelsup fields of any
! * ExpressionParams referring to values above this level.
! */
! args = (List *) replace_expression_param((Node *) args, newarg);
! newarg = NULL; /* not needed anymore */
! }
}
/* Simplify the WHEN clauses */
newargs = NIL;
const_true_cond = false;
! foreach(arg, args)
{
CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
Node *casecond;
***************
*** 2803,2810 **** eval_const_expressions_mutator(Node *node,
eval_const_expressions_mutator((Node *) caseexpr->defresult,
context);
- context->case_val = save_case_val;
-
/* If no non-FALSE alternatives, CASE reduces to the default result */
if (newargs == NIL)
return defresult;
--- 2829,2834 ----
***************
*** 2818,2835 **** eval_const_expressions_mutator(Node *node,
newcase->location = caseexpr->location;
return (Node *) newcase;
}
- if (IsA(node, CaseTestExpr))
- {
- /*
- * If we know a constant test value for the current CASE construct,
- * substitute it for the placeholder. Else just return the
- * placeholder as-is.
- */
- if (context->case_val)
- return copyObject(context->case_val);
- else
- return copyObject(node);
- }
if (IsA(node, ArrayExpr))
{
ArrayExpr *arrayexpr = (ArrayExpr *) node;
--- 2842,2847 ----
***************
*** 4139,4144 **** fail:
--- 4151,4287 ----
}
/*
+ * Returns the number of expression params pushed to the stack by this node.
+ */
+ static int
+ expression_params_used(Node *node)
+ {
+ /*
+ * "CASE x WHEN ..." constructs push the CASE-value x to an expression
+ * param.
+ */
+ if (IsA(node, CaseExpr))
+ {
+ CaseExpr *caseexpr = (CaseExpr *) node;
+ /*
+ * Note: these conditions must match those in
+ * eval_const_expressions_mutator()!
+ */
+ if (caseexpr->arg && !IsA(caseexpr->arg, Const))
+ return 1;
+ }
+ if (IsA(node, FieldStore))
+ {
+ FieldStore *fstore = (FieldStore *) node;
+ if (fstore->useparam)
+ return 1;
+ }
+ if (IsA(node, ArrayRef))
+ {
+ ArrayRef *aref = (ArrayRef *) node;
+ if (aref->refexpr)
+ return 1;
+ }
+ /*
+ * XXX
+ *
+ * IN will push one param, the left-hand value. BETWEEN will also push one,
+ * BETWEEN SYMMETRIC will push three values in the worst case.
+ */
+ return 0;
+ }
+
+ /*
+ *
+ */
+ static Node *
+ adjust_expression_params(Node *node, int offset)
+ {
+ if (offset == 0)
+ return node;
+
+ return adjust_expression_params_mutator(node, &offset);
+ }
+
+ static Node *
+ adjust_expression_params_mutator(Node *node, int *offset)
+ {
+ if (node == NULL)
+ return NULL;
+ if (IsA(node, ExpressionParam))
+ {
+ ExpressionParam *eparam = (ExpressionParam *) copyObject(node);
+ eparam->levelsup += (*offset);
+ return (Node *) eparam;
+ }
+ return expression_tree_mutator(node, adjust_expression_params_mutator,
+ (void *) offset);
+ }
+
+ /*
+ * Replaces any ExpressionParams (with eparamid == 0) in 'node' with the
+ * given replacement node.
+ *
+ * The difficulty is in tracking which ExpressionParams point to the value
+ * we're trying to replace. As we drill into a CaseExpr, for example, the
+ * "current" depth we're at increases. Within one such a node the
+ * ExpressionParams we need to replace will have levelsup == 1.
+ */
+ static Node *
+ replace_expression_param(Node *node, Node *replacement)
+ {
+ replace_expression_param_context context;
+
+ /* we are replacing ExpressionParams with levelsup 0 initially */
+ context.replacement_level = 0;
+ context.replacement = replacement;
+ return replace_expression_param_mutator(node, &context);
+ }
+
+ static Node *
+ replace_expression_param_mutator(Node *node, replace_expression_param_context *context)
+ {
+ int num_expr_params;
+
+ if (node == NULL)
+ return NULL;
+ if (IsA(node, ExpressionParam))
+ {
+ ExpressionParam *eparam = (ExpressionParam *) copyObject(node);
+
+ /*
+ * If this param points to the param that we're replacing, return
+ * the replacement. If it points to something above it, shift the
+ * reference down as we're removing the layer from the middle. If
+ * it points to something below it, leave it alone.
+ */
+ if (eparam->levelsup == context->replacement_level)
+ return context->replacement; /* XXX copy? */
+ else if (eparam->levelsup > context->replacement_level)
+ {
+ ExpressionParam *neweparam = (ExpressionParam *) copyObject(eparam);
+ neweparam->levelsup--;
+ return (Node *) neweparam;
+ }
+ else
+ return (Node *) eparam;
+ }
+
+ num_expr_params = expression_params_used(node);
+ if (num_expr_params > 0)
+ {
+ context->replacement_level += num_expr_params;
+ return expression_tree_mutator(node, replace_expression_param_mutator,
+ (void *) context);
+ context->replacement_level -= num_expr_params;
+ }
+ else
+ return expression_tree_mutator(node, replace_expression_param_mutator,
+ (void *) context);
+ }
+
+
+ /*
* Replace Param nodes by appropriate actual parameters
*/
static Node *
***************
*** 4150,4155 **** substitute_actual_parameters(Node *expr, int nargs, List *args,
--- 4293,4299 ----
context.nargs = nargs;
context.args = args;
context.usecounts = usecounts;
+ context.expr_param_depth = 0;
return substitute_actual_parameters_mutator(expr, &context);
}
***************
*** 4158,4168 **** static Node *
--- 4302,4316 ----
substitute_actual_parameters_mutator(Node *node,
substitute_actual_parameters_context *context)
{
+ int num_expr_params;
+
if (node == NULL)
return NULL;
+
if (IsA(node, Param))
{
Param *param = (Param *) node;
+ Node *arg;
if (param->paramkind != PARAM_EXTERN)
elog(ERROR, "unexpected paramkind: %d", (int) param->paramkind);
***************
*** 4172,4183 **** substitute_actual_parameters_mutator(Node *node,
/* Count usage of parameter */
context->usecounts[param->paramid - 1]++;
! /* Select the appropriate actual arg and replace the Param with it */
! /* We don't need to copy at this time (it'll get done later) */
! return list_nth(context->args, param->paramid - 1);
}
! return expression_tree_mutator(node, substitute_actual_parameters_mutator,
! (void *) context);
}
/*
--- 4320,4349 ----
/* Count usage of parameter */
context->usecounts[param->paramid - 1]++;
! /*
! * Select the appropriate actual arg and replace the Param with it.
! * We don't need to copy at this time (it'll get done later).
! * However, if we're within a node that sets an ExpressionParam, we
! * need to adjust any ExpressionParam references to outer expressions
! * accordingly.
! */
! arg = list_nth(context->args, param->paramid - 1);
! if (context->expr_param_depth > 0)
! arg = adjust_expression_params(arg, context->expr_param_depth);
! return arg;
! }
!
! num_expr_params = expression_params_used(node);
! if (num_expr_params > 0)
! {
! context->expr_param_depth += num_expr_params;
! return expression_tree_mutator(node, substitute_actual_parameters_mutator,
! (void *) context);
! context->expr_param_depth -= num_expr_params;
}
! else
! return expression_tree_mutator(node, substitute_actual_parameters_mutator,
! (void *) context);
}
/*
*** a/src/backend/parser/parse_collate.c
--- b/src/backend/parser/parse_collate.c
***************
*** 598,604 **** assign_collations_walker(Node *node, assign_collations_context *context)
case T_Const:
case T_Param:
case T_CoerceToDomainValue:
! case T_CaseTestExpr:
case T_SetToDefault:
case T_CurrentOfExpr:
--- 598,604 ----
case T_Const:
case T_Param:
case T_CoerceToDomainValue:
! case T_ExpressionParam:
case T_SetToDefault:
case T_CurrentOfExpr:
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 321,327 **** transformExpr(ParseState *pstate, Node *expr)
case T_ArrayCoerceExpr:
case T_ConvertRowtypeExpr:
case T_CollateExpr:
! case T_CaseTestExpr:
case T_ArrayExpr:
case T_CoerceToDomain:
case T_CoerceToDomainValue:
--- 321,327 ----
case T_ArrayCoerceExpr:
case T_ConvertRowtypeExpr:
case T_CollateExpr:
! case T_ExpressionParam:
case T_ArrayExpr:
case T_CoerceToDomain:
case T_CoerceToDomainValue:
***************
*** 1253,1259 **** transformCaseExpr(ParseState *pstate, CaseExpr *c)
{
CaseExpr *newc;
Node *arg;
! CaseTestExpr *placeholder;
List *newargs;
List *resultexprs;
ListCell *l;
--- 1253,1259 ----
{
CaseExpr *newc;
Node *arg;
! ExpressionParam *placeholder;
List *newargs;
List *resultexprs;
ListCell *l;
***************
*** 1286,1296 **** transformCaseExpr(ParseState *pstate, CaseExpr *c)
* Run collation assignment on the test expression so that we know
* what collation to mark the placeholder with. In principle we could
* leave it to parse_collate.c to do that later, but propagating the
! * result to the CaseTestExpr would be unnecessarily complicated.
*/
assign_expr_collations(pstate, arg);
! placeholder = makeNode(CaseTestExpr);
placeholder->typeId = exprType(arg);
placeholder->typeMod = exprTypmod(arg);
placeholder->collation = exprCollation(arg);
--- 1286,1297 ----
* Run collation assignment on the test expression so that we know
* what collation to mark the placeholder with. In principle we could
* leave it to parse_collate.c to do that later, but propagating the
! * result to the ExpressionParam would be unnecessarily complicated.
*/
assign_expr_collations(pstate, arg);
! placeholder = makeNode(ExpressionParam);
! placeholder->levelsup = 0;
placeholder->typeId = exprType(arg);
placeholder->typeMod = exprTypmod(arg);
placeholder->collation = exprCollation(arg);
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
***************
*** 583,598 **** transformAssignmentIndirection(ParseState *pstate,
List *subscripts = NIL;
bool isSlice = false;
ListCell *i;
if (indirection && !basenode)
{
! /* Set up a substitution. We reuse CaseTestExpr for this. */
! CaseTestExpr *ctest = makeNode(CaseTestExpr);
!
! ctest->typeId = targetTypeId;
! ctest->typeMod = targetTypMod;
! ctest->collation = targetCollation;
! basenode = (Node *) ctest;
}
/*
--- 583,601 ----
List *subscripts = NIL;
bool isSlice = false;
ListCell *i;
+ bool useparam = false;
if (indirection && !basenode)
{
! /* Set up a substitution. */
! ExpressionParam *eparam = makeNode(ExpressionParam);
!
! eparam->levelsup = 0;
! eparam->typeId = targetTypeId;
! eparam->typeMod = targetTypMod;
! eparam->collation = targetCollation;
! basenode = (Node *) eparam;
! useparam = true;
}
/*
***************
*** 691,696 **** transformAssignmentIndirection(ParseState *pstate,
--- 694,704 ----
fstore->arg = (Expr *) basenode;
fstore->newvals = list_make1(rhs);
fstore->fieldnums = list_make1_int(attnum);
+ /*
+ * if there's any more recursion, need to store the old value
+ * of the field in an expression param at execution time
+ */
+ fstore->useparam = (lnext(i) != NULL);
fstore->resulttype = targetTypeId;
return (Node *) fstore;
***************
*** 765,770 **** transformAssignmentSubscripts(ParseState *pstate,
--- 773,779 ----
Node *rhs,
int location)
{
+ ArrayRef *aref;
Node *result;
Oid arrayType;
int32 arrayTypMod;
***************
*** 805,817 **** transformAssignmentSubscripts(ParseState *pstate,
location);
/* process subscripts */
! result = (Node *) transformArraySubscripts(pstate,
! basenode,
! arrayType,
! elementTypeId,
! arrayTypMod,
! subscripts,
! rhs);
/* If target was a domain over array, need to coerce up to the domain */
if (arrayType != targetTypeId)
--- 814,854 ----
location);
/* process subscripts */
! aref = transformArraySubscripts(pstate,
! basenode,
! arrayType,
! elementTypeId,
! arrayTypMod,
! subscripts,
! rhs);
! /*
! * We might have a nested-assignment situation, in which the rhs is
! * itself a FieldStore or ArrayRef that needs to obtain and modify the
! * previous value of the array element or slice being replaced. If so, we
! * have to extract that value from the array at runtime and pass it down
! * via an expression param.
! *
! * Since fetching the old element might be a nontrivial expense, do it
! * only if the argument appears to actually need it. We set refuseparam
! * if that is needed.
! */
! if (IsA(rhs, FieldStore))
! {
! FieldStore *fstore = (FieldStore *) rhs;
!
! if (fstore->arg && IsA(fstore->arg, ExpressionParam) &&
! ((ExpressionParam *) fstore->arg)->levelsup == 0)
! aref->refuseparam = true;
! }
! else if (IsA(rhs, ArrayRef))
! {
! ArrayRef *arrayRef = (ArrayRef *) rhs;
!
! if (arrayRef->refexpr && IsA(arrayRef->refexpr, ExpressionParam) &&
! ((ExpressionParam *) arrayRef->refexpr)->levelsup == 0)
! aref->refuseparam = true;
! }
! result = (Node *) aref;
/* If target was a domain over array, need to coerce up to the domain */
if (arrayType != targetTypeId)
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 4783,4796 **** get_rule_expr(Node *node, deparse_context *context,
bool need_parens;
/*
! * If the argument is a CaseTestExpr, we must be inside a
* FieldStore, ie, we are assigning to an element of an array
* within a composite column. Since we already punted on
* displaying the FieldStore's target information, just punt
* here too, and display only the assignment source
* expression.
*/
! if (IsA(aref->refexpr, CaseTestExpr))
{
Assert(aref->refassgnexpr);
get_rule_expr((Node *) aref->refassgnexpr,
--- 4783,4797 ----
bool need_parens;
/*
! * If the argument is an ExpressionParam, we must be inside a
* FieldStore, ie, we are assigning to an element of an array
* within a composite column. Since we already punted on
* displaying the FieldStore's target information, just punt
* here too, and display only the assignment source
* expression.
*/
! if (IsA(aref->refexpr, ExpressionParam) &&
! ((ExpressionParam *) aref->refexpr)->levelsup == 0)
{
Assert(aref->refassgnexpr);
get_rule_expr((Node *) aref->refassgnexpr,
***************
*** 5191,5198 **** get_rule_expr(Node *node, deparse_context *context,
{
/*
* The parser should have produced WHEN clauses of
! * the form "CaseTestExpr = RHS", possibly with an
! * implicit coercion inserted above the CaseTestExpr.
* For accurate decompilation of rules it's essential
* that we show just the RHS. However in an
* expression that's been through the optimizer, the
--- 5192,5199 ----
{
/*
* The parser should have produced WHEN clauses of
! * the form "ExpressionParam[0] = RHS", possibly with an
! * implicit coercion inserted above the ExpressionParam.
* For accurate decompilation of rules it's essential
* that we show just the RHS. However in an
* expression that's been through the optimizer, the
***************
*** 5204,5214 **** get_rule_expr(Node *node, deparse_context *context,
if (IsA(w, OpExpr))
{
List *args = ((OpExpr *) w)->args;
! if (list_length(args) == 2 &&
! IsA(strip_implicit_coercions(linitial(args)),
! CaseTestExpr))
! w = (Node *) lsecond(args);
}
}
--- 5205,5218 ----
if (IsA(w, OpExpr))
{
List *args = ((OpExpr *) w)->args;
+ if (list_length(args) == 2)
+ {
+ Node *leftarg = strip_implicit_coercions(linitial(args));
! if (IsA(leftarg, ExpressionParam) &&
! ((ExpressionParam *) leftarg)->levelsup == 0)
! w = (Node *) lsecond(args);
! }
}
}
***************
*** 5232,5238 **** get_rule_expr(Node *node, deparse_context *context,
}
break;
! case T_CaseTestExpr:
{
/*
* Normally we should never get here, since for expressions
--- 5236,5242 ----
}
break;
! case T_ExpressionParam:
{
/*
* Normally we should never get here, since for expressions
***************
*** 5241,5247 **** get_rule_expr(Node *node, deparse_context *context,
* be unable to avoid that (see comments for CaseExpr). If we
* do see one, print it as CASE_TEST_EXPR.
*/
! appendStringInfo(buf, "CASE_TEST_EXPR");
}
break;
--- 5245,5252 ----
* be unable to avoid that (see comments for CaseExpr). If we
* do see one, print it as CASE_TEST_EXPR.
*/
! appendStringInfo(buf, "EXPR_PARAM[%d]",
! ((ExpressionParam *) node)->levelsup);
}
break;
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
***************
*** 1836,1842 **** scalararraysel(PlannerInfo *root,
}
else
{
! CaseTestExpr *dummyexpr;
List *args;
Selectivity s2;
int i;
--- 1836,1842 ----
}
else
{
! ExpressionParam *dummyexpr;
List *args;
Selectivity s2;
int i;
***************
*** 1844,1852 **** scalararraysel(PlannerInfo *root,
/*
* We need a dummy rightop to pass to the operator selectivity
* routine. It can be pretty much anything that doesn't look like a
! * constant; CaseTestExpr is a convenient choice.
*/
! dummyexpr = makeNode(CaseTestExpr);
dummyexpr->typeId = nominal_element_type;
dummyexpr->typeMod = -1;
dummyexpr->collation = clause->inputcollid;
--- 1844,1853 ----
/*
* We need a dummy rightop to pass to the operator selectivity
* routine. It can be pretty much anything that doesn't look like a
! * constant; ExpressionParam is a convenient choice.
*/
! dummyexpr = makeNode(ExpressionParam);
! dummyexpr->levelsup = 0;
dummyexpr->typeId = nominal_element_type;
dummyexpr->typeMod = -1;
dummyexpr->collation = clause->inputcollid;
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 125,130 **** typedef struct ExprContext
--- 125,132 ----
ParamExecData *ecxt_param_exec_vals; /* for PARAM_EXEC params */
ParamListInfo ecxt_param_list_info; /* for other param types */
+ List *expr_params; /* stack of ParamExecDatas */
+
/*
* Values to substitute for Aggref nodes in the expressions of an Agg
* node, or for WindowFunc nodes within a WindowAgg node.
***************
*** 132,141 **** typedef struct ExprContext
Datum *ecxt_aggvalues; /* precomputed values for aggs/windowfuncs */
bool *ecxt_aggnulls; /* null flags for aggs/windowfuncs */
- /* Value to substitute for CaseTestExpr nodes in expression */
- Datum caseValue_datum;
- bool caseValue_isNull;
-
/* Value to substitute for CoerceToDomainValue nodes in expression */
Datum domainValue_datum;
bool domainValue_isNull;
--- 134,139 ----
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 152,158 **** typedef enum NodeTag
T_CollateExpr,
T_CaseExpr,
T_CaseWhen,
! T_CaseTestExpr,
T_ArrayExpr,
T_RowExpr,
T_RowCompareExpr,
--- 152,158 ----
T_CollateExpr,
T_CaseExpr,
T_CaseWhen,
! T_ExpressionParam,
T_ArrayExpr,
T_RowExpr,
T_RowCompareExpr,
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 212,217 **** typedef struct Param
--- 212,239 ----
} Param;
/*
+ * ExpressionParam
+ *
+ * Expression params are similar to Params, but are only used within a single
+ * expression. An enclosing Expr node can set a param, and an inner Expr
+ * can reference it using an ExpressionParam node. Unlike normal Params,
+ * expression param ids are not globally unique. Instead, they form a stack.
+ * An ExpressionParam with eparamid == 0 references the param value at the
+ * top of the stack, eparamid == 1 the one below it, and so forth. The syntax
+ * of the expressions that use this only allow referencing the topmost param,
+ * but SQL function inlining can rearrange things.
+ */
+ typedef struct ExpressionParam
+ {
+ Expr xpr;
+ int levelsup; /* stack level this refers to */
+ Oid typeId; /* pg_type OID of parameter's datatype */
+ int32 typeMod; /* typmod value, if known */
+ Oid collation; /* OID of collation, or InvalidOid if none */
+ int location; /* token location, or -1 if unknown */
+ } ExpressionParam;
+
+ /*
* Aggref
*
* The aggregate's args list is a targetlist, ie, a list of TargetEntry nodes
***************
*** 295,300 **** typedef struct ArrayRef
--- 317,324 ----
* value */
Expr *refassgnexpr; /* expression for the source value, or NULL if
* fetch */
+ bool refuseparam; /* need an exec param to hold old value during
+ * refassgnexpr execution? */
} ArrayRef;
/*
***************
*** 639,644 **** typedef struct FieldStore
--- 663,669 ----
Expr *arg; /* input tuple value */
List *newvals; /* new value(s) for field(s) */
List *fieldnums; /* integer list of field attnums */
+ bool useparam; /* need an expr param to hold old values? */
Oid resulttype; /* type of result (same as type of arg) */
/* Like RowExpr, we deliberately omit a typmod and collation here */
} FieldStore;
***************
*** 761,768 **** typedef struct CollateExpr
* In the raw grammar output for the second form, the condition expressions
* of the WHEN clauses are just the comparison values. Parse analysis
* converts these to valid boolean expressions of the form
! * CaseTestExpr '=' compexpr
! * where the CaseTestExpr node is a placeholder that emits the correct
* value at runtime. This structure is used so that the testexpr need be
* evaluated only once. Note that after parse analysis, the condition
* expressions always yield boolean.
--- 786,793 ----
* In the raw grammar output for the second form, the condition expressions
* of the WHEN clauses are just the comparison values. Parse analysis
* converts these to valid boolean expressions of the form
! * ExpressionParam[0] '=' compexpr
! * where the ExpressionParam node is a placeholder that emits the correct
* value at runtime. This structure is used so that the testexpr need be
* evaluated only once. Note that after parse analysis, the condition
* expressions always yield boolean.
***************
*** 794,815 **** typedef struct CaseWhen
} CaseWhen;
/*
- * Placeholder node for the test value to be processed by a CASE expression.
- * This is effectively like a Param, but can be implemented more simply
- * since we need only one replacement value at a time.
- *
- * We also use this in nested UPDATE expressions.
- * See transformAssignmentIndirection().
- */
- typedef struct CaseTestExpr
- {
- Expr xpr;
- Oid typeId; /* type for substituted value */
- int32 typeMod; /* typemod for substituted value */
- Oid collation; /* collation for the substituted value */
- } CaseTestExpr;
-
- /*
* ArrayExpr - an ARRAY[] expression
*
* Note: if multidims is false, the constituent expressions all yield the
--- 819,824 ----
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 5459,5465 **** exec_simple_check_node(Node *node)
return TRUE;
}
! case T_CaseTestExpr:
return TRUE;
case T_ArrayExpr:
--- 5459,5465 ----
return TRUE;
}
! case T_ExpressionParam:
return TRUE;
case T_ArrayExpr:
Hello
2011/6/3 Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>:
On 31.05.2011 19:10, Heikki Linnakangas wrote:
For index expressions, we could use a function similar to
ChangeVarNodes(), that shifts all the paramids in the already-planned
expression, preparing it for inclusion within the enclosing plan. I'm a
bit worried that that might screw up the logic used to compare if an
expression matches the index expression, though; the param ids in the
two expressions won't match.Yeah, the expression comparison logic gets all confused by this :-(. I
couldn't figure out a way to make it work without making the comparison a
whole lot more complicated than it is today. I'm going back to my original
thoughts of a new kind of node to replace CaseTestExpr, which allows
referencing values from upper levels in the expression tree.So, here's a WIP patch using that approach. I've replaced CaseTestExpr with
ExpressionParam. ExpressionParam has a levelsup field, which is similar to
varlevelsup in Var. With levelsup == 0, ExpressionParam works just like
CaseTestExpr did. With levelsup == 1, it refers to the value from above the
enclosing CaseExpr (or any other node that uses these
ExpressionParams/CaseTestExprs).
I have a query - should be ExpressionParam used for removing a
multiple function call when composite result is expanded?
With some similar optimization a SELECT (fce(x)).* FROM tab will be optimal
Regards
Pavel Stěhule
Show quoted text
The complicated part is to ensure that levelsup is always set correctly. At
parse time, levelsup is always set to 0, as the syntax doesn't allow
referencing upper levels directly. When an SQL function is inlined, any
ExpressionParams in the expressions that are substituted for Params need to
have their levelsup adjusted, so that it still refers to the right value if
there's CASE expressions in the inlined function. Also, when an
ExpressionParam is replaced with a Const, the levelsup fields of any other
ExpressionParams within the CaseExpr referring to higher levels need to have
their levelsup decremented to account for the fact that the CaseExpr doesn't
push the expression parameter anymore.At execution time, the expression parameters form a stack. We've always done
the save-restore logic, but the stack is now represented explicitly as a
List in ExprContext. When an ExpressionParam is evaluated, the nth element
is fetched from the stack, corresponding to levelsup.--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 16.06.2011 22:46, Pavel Stehule wrote:
I have a query - should be ExpressionParam used for removing a
multiple function call when composite result is expanded?With some similar optimization a SELECT (fce(x)).* FROM tab will be optimal
Hmm, I don't think it will work as is for that purpose, as each
targetlist item is a separate expression, and ExpressionParams only work
within an expression.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
The complicated part is to ensure that levelsup is always set correctly.
At parse time, levelsup is always set to 0, as the syntax doesn't allow
referencing upper levels directly. When an SQL function is inlined, any
ExpressionParams in the expressions that are substituted for Params need
to have their levelsup adjusted, so that it still refers to the right
value if there's CASE expressions in the inlined function. Also, when an
ExpressionParam is replaced with a Const, the levelsup fields of any
other ExpressionParams within the CaseExpr referring to higher levels
need to have their levelsup decremented to account for the fact that the
CaseExpr doesn't push the expression parameter anymore.
I believe this is an unworkably complex, and almost certainly buggy
Rube Goldberg device. Even if it manages to work today, it's going to
be impossible to maintain those levelsup values correctly during
any sort of expression rearrangement or optimization.
Please take another look at just assigning a PARAM_EXEC parameter per
Case expression.
regards, tom lane
On 16.06.2011 23:56, Tom Lane wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
The complicated part is to ensure that levelsup is always set correctly.
At parse time, levelsup is always set to 0, as the syntax doesn't allow
referencing upper levels directly. When an SQL function is inlined, any
ExpressionParams in the expressions that are substituted for Params need
to have their levelsup adjusted, so that it still refers to the right
value if there's CASE expressions in the inlined function. Also, when an
ExpressionParam is replaced with a Const, the levelsup fields of any
other ExpressionParams within the CaseExpr referring to higher levels
need to have their levelsup decremented to account for the fact that the
CaseExpr doesn't push the expression parameter anymore.I believe this is an unworkably complex, and almost certainly buggy
Rube Goldberg device. Even if it manages to work today, it's going to
be impossible to maintain those levelsup values correctly during
any sort of expression rearrangement or optimization.Please take another look at just assigning a PARAM_EXEC parameter per
Case expression.
I've added this to the TODO list, hopefully someone more skilled with
the planner than me will pick this up...
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com