Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a477bfc

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 3acfe6b commit a477bfc

File tree

3 files changed

+101
-97
lines changed

3 files changed

+101
-97
lines changed

src/backend/optimizer/util/clauses.c

+82-97
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);
@@ -2774,46 +2777,19 @@ eval_const_expressions_mutator(Node *node,
27742777
return node;
27752778
case T_RelabelType:
27762779
{
2777-
/*
2778-
* If we can simplify the input to a constant, then we don't
2779-
* need the RelabelType node anymore: just change the type
2780-
* field of the Const node. Otherwise, must copy the
2781-
* RelabelType node.
2782-
*/
27832780
RelabelType *relabel = (RelabelType *) node;
27842781
Node *arg;
27852782

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

2935+
/* Simplify the input ... */
29602936
arg = eval_const_expressions_mutator((Node *) collate->arg,
29612937
context);
2962-
2963-
if (arg && IsA(arg, Const))
2964-
{
2965-
Const *con = (Const *) arg;
2966-
2967-
con->constcollid = collate->collOid;
2968-
return (Node *) con;
2969-
}
2970-
else if (collate->collOid == exprCollation(arg))
2971-
{
2972-
/* Don't need a RelabelType either... */
2973-
return arg;
2974-
}
2975-
else
2976-
{
2977-
RelabelType *relabel = makeNode(RelabelType);
2978-
2979-
relabel->resulttype = exprType(arg);
2980-
relabel->resulttypmod = exprTypmod(arg);
2981-
relabel->resultcollid = collate->collOid;
2982-
relabel->relabelformat = COERCE_IMPLICIT_CAST;
2983-
relabel->location = collate->location;
2984-
2985-
/* Don't create stacked RelabelTypes */
2986-
while (arg && IsA(arg, RelabelType))
2987-
arg = (Node *) ((RelabelType *) arg)->arg;
2988-
relabel->arg = (Expr *) arg;
2989-
2990-
return (Node *) relabel;
2991-
}
2938+
/* ... and attach a new RelabelType node, if needed */
2939+
return apply_const_relabel(arg,
2940+
exprType(arg),
2941+
exprTypmod(arg),
2942+
collate->collOid,
2943+
COERCE_IMPLICIT_CAST,
2944+
collate->location);
29922945
}
29932946
case T_CaseExpr:
29942947
{
@@ -3490,32 +3443,12 @@ eval_const_expressions_mutator(Node *node,
34903443
cdomain->resulttype);
34913444

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

35213454
newcdomain = makeNode(CoerceToDomain);
@@ -3648,6 +3581,58 @@ ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
36483581
return false;
36493582
}
36503583

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

src/test/regress/expected/equivclass.out

+12
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

+7
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)