Skip to content

Commit 3decd15

Browse files
committed
Teach eval_const_expressions() to handle some more cases.
Add some infrastructure (mostly macros) to make it easier to write typical cases for constant-expression simplification. Add simplification processing for ArrayRef, RowExpr, and ScalarArrayOpExpr node types, which formerly went unsimplified even if all their inputs were constants. Also teach it to simplify FieldSelect from a composite constant. Make use of the new infrastructure to reduce the amount of code needed for the existing ArrayExpr and ArrayCoerceExpr cases. One existing test case changes output as a result of the fact that RowExpr can now be folded to a constant. All the new code is exercised by existing test cases according to gcov, so I feel no need to add additional tests. Tom Lane, reviewed by Dmitry Dolgov Discussion: https://postgr.es/m/3be3b82c-e29c-b674-2163-bf47d98817b1@iki.fi
1 parent 35c0754 commit 3decd15

File tree

2 files changed

+150
-75
lines changed

2 files changed

+150
-75
lines changed

src/backend/optimizer/util/clauses.c

Lines changed: 147 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ static List *find_nonnullable_vars_walker(Node *node, bool top_level);
115115
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
116116
static Node *eval_const_expressions_mutator(Node *node,
117117
eval_const_expressions_context *context);
118+
static bool contain_non_const_walker(Node *node, void *context);
119+
static bool ece_function_is_safe(Oid funcid,
120+
eval_const_expressions_context *context);
118121
static List *simplify_or_arguments(List *args,
119122
eval_const_expressions_context *context,
120123
bool *haveNull, bool *forceTrue);
@@ -2502,6 +2505,37 @@ estimate_expression_value(PlannerInfo *root, Node *node)
25022505
return eval_const_expressions_mutator(node, &context);
25032506
}
25042507

2508+
/*
2509+
* The generic case in eval_const_expressions_mutator is to recurse using
2510+
* expression_tree_mutator, which will copy the given node unchanged but
2511+
* const-simplify its arguments (if any) as far as possible. If the node
2512+
* itself does immutable processing, and each of its arguments were reduced
2513+
* to a Const, we can then reduce it to a Const using evaluate_expr. (Some
2514+
* node types need more complicated logic; for example, a CASE expression
2515+
* might be reducible to a constant even if not all its subtrees are.)
2516+
*/
2517+
#define ece_generic_processing(node) \
2518+
expression_tree_mutator((Node *) (node), eval_const_expressions_mutator, \
2519+
(void *) context)
2520+
2521+
/*
2522+
* Check whether all arguments of the given node were reduced to Consts.
2523+
* By going directly to expression_tree_walker, contain_non_const_walker
2524+
* is not applied to the node itself, only to its children.
2525+
*/
2526+
#define ece_all_arguments_const(node) \
2527+
(!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL))
2528+
2529+
/* Generic macro for applying evaluate_expr */
2530+
#define ece_evaluate_expr(node) \
2531+
((Node *) evaluate_expr((Expr *) (node), \
2532+
exprType((Node *) (node)), \
2533+
exprTypmod((Node *) (node)), \
2534+
exprCollation((Node *) (node))))
2535+
2536+
/*
2537+
* Recursive guts of eval_const_expressions/estimate_expression_value
2538+
*/
25052539
static Node *
25062540
eval_const_expressions_mutator(Node *node,
25072541
eval_const_expressions_context *context)
@@ -2830,6 +2864,25 @@ eval_const_expressions_mutator(Node *node,
28302864
newexpr->location = expr->location;
28312865
return (Node *) newexpr;
28322866
}
2867+
case T_ScalarArrayOpExpr:
2868+
{
2869+
ScalarArrayOpExpr *saop;
2870+
2871+
/* Copy the node and const-simplify its arguments */
2872+
saop = (ScalarArrayOpExpr *) ece_generic_processing(node);
2873+
2874+
/* Make sure we know underlying function */
2875+
set_sa_opfuncid(saop);
2876+
2877+
/*
2878+
* If all arguments are Consts, and it's a safe function, we
2879+
* can fold to a constant
2880+
*/
2881+
if (ece_all_arguments_const(saop) &&
2882+
ece_function_is_safe(saop->opfuncid, context))
2883+
return ece_evaluate_expr(saop);
2884+
return (Node *) saop;
2885+
}
28332886
case T_BoolExpr:
28342887
{
28352888
BoolExpr *expr = (BoolExpr *) node;
@@ -3054,47 +3107,24 @@ eval_const_expressions_mutator(Node *node,
30543107
}
30553108
case T_ArrayCoerceExpr:
30563109
{
3057-
ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
3058-
Expr *arg;
3059-
Expr *elemexpr;
3060-
ArrayCoerceExpr *newexpr;
3061-
3062-
/*
3063-
* Reduce constants in the ArrayCoerceExpr's argument and
3064-
* per-element expressions, then build a new ArrayCoerceExpr.
3065-
*/
3066-
arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
3067-
context);
3068-
elemexpr = (Expr *) eval_const_expressions_mutator((Node *) expr->elemexpr,
3069-
context);
3110+
ArrayCoerceExpr *ac;
30703111

3071-
newexpr = makeNode(ArrayCoerceExpr);
3072-
newexpr->arg = arg;
3073-
newexpr->elemexpr = elemexpr;
3074-
newexpr->resulttype = expr->resulttype;
3075-
newexpr->resulttypmod = expr->resulttypmod;
3076-
newexpr->resultcollid = expr->resultcollid;
3077-
newexpr->coerceformat = expr->coerceformat;
3078-
newexpr->location = expr->location;
3112+
/* Copy the node and const-simplify its arguments */
3113+
ac = (ArrayCoerceExpr *) ece_generic_processing(node);
30793114

30803115
/*
3081-
* If constant argument and per-element expression is
3116+
* If constant argument and the per-element expression is
30823117
* immutable, we can simplify the whole thing to a constant.
30833118
* Exception: although contain_mutable_functions considers
30843119
* CoerceToDomain immutable for historical reasons, let's not
30853120
* do so here; this ensures coercion to an array-over-domain
30863121
* does not apply the domain's constraints until runtime.
30873122
*/
3088-
if (arg && IsA(arg, Const) &&
3089-
elemexpr && !IsA(elemexpr, CoerceToDomain) &&
3090-
!contain_mutable_functions((Node *) elemexpr))
3091-
return (Node *) evaluate_expr((Expr *) newexpr,
3092-
newexpr->resulttype,
3093-
newexpr->resulttypmod,
3094-
newexpr->resultcollid);
3095-
3096-
/* Else we must return the partially-simplified node */
3097-
return (Node *) newexpr;
3123+
if (ac->arg && IsA(ac->arg, Const) &&
3124+
ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
3125+
!contain_mutable_functions((Node *) ac->elemexpr))
3126+
return ece_evaluate_expr(ac);
3127+
return (Node *) ac;
30983128
}
30993129
case T_CollateExpr:
31003130
{
@@ -3286,41 +3316,22 @@ eval_const_expressions_mutator(Node *node,
32863316
else
32873317
return copyObject(node);
32883318
}
3319+
case T_ArrayRef:
32893320
case T_ArrayExpr:
3321+
case T_RowExpr:
32903322
{
3291-
ArrayExpr *arrayexpr = (ArrayExpr *) node;
3292-
ArrayExpr *newarray;
3293-
bool all_const = true;
3294-
List *newelems;
3295-
ListCell *element;
3296-
3297-
newelems = NIL;
3298-
foreach(element, arrayexpr->elements)
3299-
{
3300-
Node *e;
3301-
3302-
e = eval_const_expressions_mutator((Node *) lfirst(element),
3303-
context);
3304-
if (!IsA(e, Const))
3305-
all_const = false;
3306-
newelems = lappend(newelems, e);
3307-
}
3323+
/*
3324+
* Generic handling for node types whose own processing is
3325+
* known to be immutable, and for which we need no smarts
3326+
* beyond "simplify if all inputs are constants".
3327+
*/
33083328

3309-
newarray = makeNode(ArrayExpr);
3310-
newarray->array_typeid = arrayexpr->array_typeid;
3311-
newarray->array_collid = arrayexpr->array_collid;
3312-
newarray->element_typeid = arrayexpr->element_typeid;
3313-
newarray->elements = newelems;
3314-
newarray->multidims = arrayexpr->multidims;
3315-
newarray->location = arrayexpr->location;
3316-
3317-
if (all_const)
3318-
return (Node *) evaluate_expr((Expr *) newarray,
3319-
newarray->array_typeid,
3320-
exprTypmod(node),
3321-
newarray->array_collid);
3322-
3323-
return (Node *) newarray;
3329+
/* Copy the node and const-simplify its arguments */
3330+
node = ece_generic_processing(node);
3331+
/* If all arguments are Consts, we can fold to a constant */
3332+
if (ece_all_arguments_const(node))
3333+
return ece_evaluate_expr(node);
3334+
return node;
33243335
}
33253336
case T_CoalesceExpr:
33263337
{
@@ -3397,7 +3408,8 @@ eval_const_expressions_mutator(Node *node,
33973408
* simple Var. (This case won't be generated directly by the
33983409
* parser, because ParseComplexProjection short-circuits it.
33993410
* But it can arise while simplifying functions.) Also, we
3400-
* can optimize field selection from a RowExpr construct.
3411+
* can optimize field selection from a RowExpr construct, or
3412+
* of course from a constant.
34013413
*
34023414
* However, replacing a whole-row Var in this way has a
34033415
* pitfall: if we've already built the rel targetlist for the
@@ -3412,6 +3424,8 @@ eval_const_expressions_mutator(Node *node,
34123424
* We must also check that the declared type of the field is
34133425
* still the same as when the FieldSelect was created --- this
34143426
* can change if someone did ALTER COLUMN TYPE on the rowtype.
3427+
* If it isn't, we skip the optimization; the case will
3428+
* probably fail at runtime, but that's not our problem here.
34153429
*/
34163430
FieldSelect *fselect = (FieldSelect *) node;
34173431
FieldSelect *newfselect;
@@ -3462,6 +3476,17 @@ eval_const_expressions_mutator(Node *node,
34623476
newfselect->resulttype = fselect->resulttype;
34633477
newfselect->resulttypmod = fselect->resulttypmod;
34643478
newfselect->resultcollid = fselect->resultcollid;
3479+
if (arg && IsA(arg, Const))
3480+
{
3481+
Const *con = (Const *) arg;
3482+
3483+
if (rowtype_field_matches(con->consttype,
3484+
newfselect->fieldnum,
3485+
newfselect->resulttype,
3486+
newfselect->resulttypmod,
3487+
newfselect->resultcollid))
3488+
return ece_evaluate_expr(newfselect);
3489+
}
34653490
return (Node *) newfselect;
34663491
}
34673492
case T_NullTest:
@@ -3557,6 +3582,13 @@ eval_const_expressions_mutator(Node *node,
35573582
}
35583583
case T_BooleanTest:
35593584
{
3585+
/*
3586+
* This case could be folded into the generic handling used
3587+
* for ArrayRef etc. But because the simplification logic is
3588+
* so trivial, applying evaluate_expr() to perform it would be
3589+
* a heavy overhead. BooleanTest is probably common enough to
3590+
* justify keeping this bespoke implementation.
3591+
*/
35603592
BooleanTest *btest = (BooleanTest *) node;
35613593
BooleanTest *newbtest;
35623594
Node *arg;
@@ -3630,14 +3662,57 @@ eval_const_expressions_mutator(Node *node,
36303662
}
36313663

36323664
/*
3633-
* For any node type not handled above, we recurse using
3634-
* expression_tree_mutator, which will copy the node unchanged but try to
3635-
* simplify its arguments (if any) using this routine. For example: we
3636-
* cannot eliminate an ArrayRef node, but we might be able to simplify
3637-
* constant expressions in its subscripts.
3665+
* For any node type not handled above, copy the node unchanged but
3666+
* const-simplify its subexpressions. This is the correct thing for node
3667+
* types whose behavior might change between planning and execution, such
3668+
* as CoerceToDomain. It's also a safe default for new node types not
3669+
* known to this routine.
36383670
*/
3639-
return expression_tree_mutator(node, eval_const_expressions_mutator,
3640-
(void *) context);
3671+
return ece_generic_processing(node);
3672+
}
3673+
3674+
/*
3675+
* Subroutine for eval_const_expressions: check for non-Const nodes.
3676+
*
3677+
* We can abort recursion immediately on finding a non-Const node. This is
3678+
* critical for performance, else eval_const_expressions_mutator would take
3679+
* O(N^2) time on non-simplifiable trees. However, we do need to descend
3680+
* into List nodes since expression_tree_walker sometimes invokes the walker
3681+
* function directly on List subtrees.
3682+
*/
3683+
static bool
3684+
contain_non_const_walker(Node *node, void *context)
3685+
{
3686+
if (node == NULL)
3687+
return false;
3688+
if (IsA(node, Const))
3689+
return false;
3690+
if (IsA(node, List))
3691+
return expression_tree_walker(node, contain_non_const_walker, context);
3692+
/* Otherwise, abort the tree traversal and return true */
3693+
return true;
3694+
}
3695+
3696+
/*
3697+
* Subroutine for eval_const_expressions: check if a function is OK to evaluate
3698+
*/
3699+
static bool
3700+
ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
3701+
{
3702+
char provolatile = func_volatile(funcid);
3703+
3704+
/*
3705+
* Ordinarily we are only allowed to simplify immutable functions. But for
3706+
* purposes of estimation, we consider it okay to simplify functions that
3707+
* are merely stable; the risk that the result might change from planning
3708+
* time to execution time is worth taking in preference to not being able
3709+
* to estimate the value at all.
3710+
*/
3711+
if (provolatile == PROVOLATILE_IMMUTABLE)
3712+
return true;
3713+
if (context->estimate && provolatile == PROVOLATILE_STABLE)
3714+
return true;
3715+
return false;
36413716
}
36423717

36433718
/*

src/test/regress/expected/rowtypes.out

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,10 @@ ERROR: cannot compare dissimilar column types bigint and integer at record colu
307307
explain (costs off)
308308
select * from int8_tbl i8
309309
where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
310-
QUERY PLAN
311-
-----------------------------------------------------------------------------------------------------------------
310+
QUERY PLAN
311+
-------------------------------------------------------------------------------
312312
Seq Scan on int8_tbl i8
313-
Filter: (i8.* = ANY (ARRAY[ROW('123'::bigint, '456'::bigint)::int8_tbl, '(4567890123456789,123)'::int8_tbl]))
313+
Filter: (i8.* = ANY ('{"(123,456)","(4567890123456789,123)"}'::int8_tbl[]))
314314
(2 rows)
315315

316316
select * from int8_tbl i8

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy