Skip to content

Commit 30d5c6b

Browse files
committed
Suppress unnecessary RelabelType nodes in more cases.
eval_const_expressions sometimes produced RelabelType nodes that were useless because they just relabeled an expression to the same exposed type it already had. This is worth avoiding because it can cause two equivalent expressions to not be equal(), preventing recognition of useful optimizations. In the test case added here, an unpatched planner fails to notice that the "sqli = constant" clause renders a sort step unnecessary, because one code path produces an extra RelabelType and another doesn't. Fix by ensuring that eval_const_expressions_mutator's T_RelabelType case will not add in an unnecessary RelabelType. Also save some code by sharing a subroutine with the effectively-equivalent cases for CollateExpr and CoerceToDomain. (CollateExpr had no bug, and I think that the case couldn't arise with CoerceToDomain, but it seems prudent to do the same check for all three cases.) Back-patch to v12. In principle this has been wrong all along, but I haven't seen a case where it causes visible misbehavior before v12, so refrain from changing stable branches unnecessarily. Per investigation of a report from Eric Gillum. Discussion: https://postgr.es/m/CAMmjdmvAZsUEskHYj=KT9sTukVVCiCSoe_PBKOXsncFeAUDPCQ@mail.gmail.com
1 parent 902f40d commit 30d5c6b

File tree

3 files changed

+101
-97
lines changed

3 files changed

+101
-97
lines changed

src/backend/optimizer/util/clauses.c

Lines changed: 82 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ static Node *eval_const_expressions_mutator(Node *node,
119119
static bool contain_non_const_walker(Node *node, void *context);
120120
static bool ece_function_is_safe(Oid funcid,
121121
eval_const_expressions_context *context);
122+
static Node *apply_const_relabel(Node *arg, Oid rtype,
123+
int32 rtypmod, Oid rcollid,
124+
CoercionForm rformat, int rlocation);
122125
static List *simplify_or_arguments(List *args,
123126
eval_const_expressions_context *context,
124127
bool *haveNull, bool *forceTrue);
@@ -2776,46 +2779,19 @@ eval_const_expressions_mutator(Node *node,
27762779
return node;
27772780
case T_RelabelType:
27782781
{
2779-
/*
2780-
* If we can simplify the input to a constant, then we don't
2781-
* need the RelabelType node anymore: just change the type
2782-
* field of the Const node. Otherwise, must copy the
2783-
* RelabelType node.
2784-
*/
27852782
RelabelType *relabel = (RelabelType *) node;
27862783
Node *arg;
27872784

2785+
/* Simplify the input ... */
27882786
arg = eval_const_expressions_mutator((Node *) relabel->arg,
27892787
context);
2790-
2791-
/*
2792-
* If we find stacked RelabelTypes (eg, from foo :: int ::
2793-
* oid) we can discard all but the top one.
2794-
*/
2795-
while (arg && IsA(arg, RelabelType))
2796-
arg = (Node *) ((RelabelType *) arg)->arg;
2797-
2798-
if (arg && IsA(arg, Const))
2799-
{
2800-
Const *con = (Const *) arg;
2801-
2802-
con->consttype = relabel->resulttype;
2803-
con->consttypmod = relabel->resulttypmod;
2804-
con->constcollid = relabel->resultcollid;
2805-
return (Node *) con;
2806-
}
2807-
else
2808-
{
2809-
RelabelType *newrelabel = makeNode(RelabelType);
2810-
2811-
newrelabel->arg = (Expr *) arg;
2812-
newrelabel->resulttype = relabel->resulttype;
2813-
newrelabel->resulttypmod = relabel->resulttypmod;
2814-
newrelabel->resultcollid = relabel->resultcollid;
2815-
newrelabel->relabelformat = relabel->relabelformat;
2816-
newrelabel->location = relabel->location;
2817-
return (Node *) newrelabel;
2818-
}
2788+
/* ... and attach a new RelabelType node, if needed */
2789+
return apply_const_relabel(arg,
2790+
relabel->resulttype,
2791+
relabel->resulttypmod,
2792+
relabel->resultcollid,
2793+
relabel->relabelformat,
2794+
relabel->location);
28192795
}
28202796
case T_CoerceViaIO:
28212797
{
@@ -2949,48 +2925,25 @@ eval_const_expressions_mutator(Node *node,
29492925
case T_CollateExpr:
29502926
{
29512927
/*
2952-
* If we can simplify the input to a constant, then we don't
2953-
* need the CollateExpr node at all: just change the
2954-
* constcollid field of the Const node. Otherwise, replace
2955-
* the CollateExpr with a RelabelType. (We do that so as to
2956-
* improve uniformity of expression representation and thus
2957-
* simplify comparison of expressions.)
2928+
* We replace CollateExpr with RelabelType, so as to improve
2929+
* uniformity of expression representation and thus simplify
2930+
* comparison of expressions. Hence this looks very nearly
2931+
* the same as the RelabelType case, and we can apply the same
2932+
* optimizations to avoid unnecessary RelabelTypes.
29582933
*/
29592934
CollateExpr *collate = (CollateExpr *) node;
29602935
Node *arg;
29612936

2937+
/* Simplify the input ... */
29622938
arg = eval_const_expressions_mutator((Node *) collate->arg,
29632939
context);
2964-
2965-
if (arg && IsA(arg, Const))
2966-
{
2967-
Const *con = (Const *) arg;
2968-
2969-
con->constcollid = collate->collOid;
2970-
return (Node *) con;
2971-
}
2972-
else if (collate->collOid == exprCollation(arg))
2973-
{
2974-
/* Don't need a RelabelType either... */
2975-
return arg;
2976-
}
2977-
else
2978-
{
2979-
RelabelType *relabel = makeNode(RelabelType);
2980-
2981-
relabel->resulttype = exprType(arg);
2982-
relabel->resulttypmod = exprTypmod(arg);
2983-
relabel->resultcollid = collate->collOid;
2984-
relabel->relabelformat = COERCE_IMPLICIT_CAST;
2985-
relabel->location = collate->location;
2986-
2987-
/* Don't create stacked RelabelTypes */
2988-
while (arg && IsA(arg, RelabelType))
2989-
arg = (Node *) ((RelabelType *) arg)->arg;
2990-
relabel->arg = (Expr *) arg;
2991-
2992-
return (Node *) relabel;
2993-
}
2940+
/* ... and attach a new RelabelType node, if needed */
2941+
return apply_const_relabel(arg,
2942+
exprType(arg),
2943+
exprTypmod(arg),
2944+
collate->collOid,
2945+
COERCE_IMPLICIT_CAST,
2946+
collate->location);
29942947
}
29952948
case T_CaseExpr:
29962949
{
@@ -3492,32 +3445,12 @@ eval_const_expressions_mutator(Node *node,
34923445
cdomain->resulttype);
34933446

34943447
/* Generate RelabelType to substitute for CoerceToDomain */
3495-
/* This should match the RelabelType logic above */
3496-
3497-
while (arg && IsA(arg, RelabelType))
3498-
arg = (Node *) ((RelabelType *) arg)->arg;
3499-
3500-
if (arg && IsA(arg, Const))
3501-
{
3502-
Const *con = (Const *) arg;
3503-
3504-
con->consttype = cdomain->resulttype;
3505-
con->consttypmod = cdomain->resulttypmod;
3506-
con->constcollid = cdomain->resultcollid;
3507-
return (Node *) con;
3508-
}
3509-
else
3510-
{
3511-
RelabelType *newrelabel = makeNode(RelabelType);
3512-
3513-
newrelabel->arg = (Expr *) arg;
3514-
newrelabel->resulttype = cdomain->resulttype;
3515-
newrelabel->resulttypmod = cdomain->resulttypmod;
3516-
newrelabel->resultcollid = cdomain->resultcollid;
3517-
newrelabel->relabelformat = cdomain->coercionformat;
3518-
newrelabel->location = cdomain->location;
3519-
return (Node *) newrelabel;
3520-
}
3448+
return apply_const_relabel(arg,
3449+
cdomain->resulttype,
3450+
cdomain->resulttypmod,
3451+
cdomain->resultcollid,
3452+
cdomain->coercionformat,
3453+
cdomain->location);
35213454
}
35223455

35233456
newcdomain = makeNode(CoerceToDomain);
@@ -3650,6 +3583,58 @@ ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
36503583
return false;
36513584
}
36523585

3586+
/*
3587+
* Subroutine for eval_const_expressions: apply RelabelType if needed
3588+
*/
3589+
static Node *
3590+
apply_const_relabel(Node *arg, Oid rtype, int32 rtypmod, Oid rcollid,
3591+
CoercionForm rformat, int rlocation)
3592+
{
3593+
/*
3594+
* If we find stacked RelabelTypes (eg, from foo::int::oid) we can discard
3595+
* all but the top one, and must do so to ensure that semantically
3596+
* equivalent expressions are equal().
3597+
*/
3598+
while (arg && IsA(arg, RelabelType))
3599+
arg = (Node *) ((RelabelType *) arg)->arg;
3600+
3601+
if (arg && IsA(arg, Const))
3602+
{
3603+
/*
3604+
* If it's a Const, just modify it in-place; since this is part of
3605+
* eval_const_expressions, we want to end up with a simple Const not
3606+
* an expression tree. We assume the Const is newly generated and
3607+
* hence safe to modify.
3608+
*/
3609+
Const *con = (Const *) arg;
3610+
3611+
con->consttype = rtype;
3612+
con->consttypmod = rtypmod;
3613+
con->constcollid = rcollid;
3614+
return (Node *) con;
3615+
}
3616+
else if (exprType(arg) == rtype &&
3617+
exprTypmod(arg) == rtypmod &&
3618+
exprCollation(arg) == rcollid)
3619+
{
3620+
/* Sometimes we find a nest of relabels that net out to nothing. */
3621+
return arg;
3622+
}
3623+
else
3624+
{
3625+
/* Nope, gotta have a RelabelType. */
3626+
RelabelType *newrelabel = makeNode(RelabelType);
3627+
3628+
newrelabel->arg = (Expr *) arg;
3629+
newrelabel->resulttype = rtype;
3630+
newrelabel->resulttypmod = rtypmod;
3631+
newrelabel->resultcollid = rcollid;
3632+
newrelabel->relabelformat = rformat;
3633+
newrelabel->location = rlocation;
3634+
return (Node *) newrelabel;
3635+
}
3636+
}
3637+
36533638
/*
36543639
* Subroutine for eval_const_expressions: process arguments of an OR clause
36553640
*

src/test/regress/expected/equivclass.out

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,15 @@ explain (costs off)
439439
Filter: ((unique1 = unique1) OR (unique2 = unique2))
440440
(2 rows)
441441

442+
-- check that we recognize equivalence with dummy domains in the way
443+
create temp table undername (f1 name, f2 int);
444+
create temp view overview as
445+
select f1::information_schema.sql_identifier as sqli, f2 from undername;
446+
explain (costs off) -- this should not require a sort
447+
select * from overview where sqli = 'foo' order by sqli;
448+
QUERY PLAN
449+
------------------------------
450+
Seq Scan on undername
451+
Filter: (f1 = 'foo'::name)
452+
(2 rows)
453+

src/test/regress/sql/equivclass.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,10 @@ explain (costs off)
262262
-- this could be converted, but isn't at present
263263
explain (costs off)
264264
select * from tenk1 where unique1 = unique1 or unique2 = unique2;
265+
266+
-- check that we recognize equivalence with dummy domains in the way
267+
create temp table undername (f1 name, f2 int);
268+
create temp view overview as
269+
select f1::information_schema.sql_identifier as sqli, f2 from undername;
270+
explain (costs off) -- this should not require a sort
271+
select * from overview where sqli = 'foo' order by sqli;

0 commit comments

Comments
 (0)