mirror of
https://github.com/postgres/postgres.git
synced 2025-05-02 11:44:50 +03:00
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
This commit is contained in:
parent
35c0754fad
commit
3decd150a2
@ -115,6 +115,9 @@ static List *find_nonnullable_vars_walker(Node *node, bool top_level);
|
|||||||
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
|
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
|
||||||
static Node *eval_const_expressions_mutator(Node *node,
|
static Node *eval_const_expressions_mutator(Node *node,
|
||||||
eval_const_expressions_context *context);
|
eval_const_expressions_context *context);
|
||||||
|
static bool contain_non_const_walker(Node *node, void *context);
|
||||||
|
static bool ece_function_is_safe(Oid funcid,
|
||||||
|
eval_const_expressions_context *context);
|
||||||
static List *simplify_or_arguments(List *args,
|
static List *simplify_or_arguments(List *args,
|
||||||
eval_const_expressions_context *context,
|
eval_const_expressions_context *context,
|
||||||
bool *haveNull, bool *forceTrue);
|
bool *haveNull, bool *forceTrue);
|
||||||
@ -2502,6 +2505,37 @@ estimate_expression_value(PlannerInfo *root, Node *node)
|
|||||||
return eval_const_expressions_mutator(node, &context);
|
return eval_const_expressions_mutator(node, &context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The generic case in eval_const_expressions_mutator is to recurse using
|
||||||
|
* expression_tree_mutator, which will copy the given node unchanged but
|
||||||
|
* const-simplify its arguments (if any) as far as possible. If the node
|
||||||
|
* itself does immutable processing, and each of its arguments were reduced
|
||||||
|
* to a Const, we can then reduce it to a Const using evaluate_expr. (Some
|
||||||
|
* node types need more complicated logic; for example, a CASE expression
|
||||||
|
* might be reducible to a constant even if not all its subtrees are.)
|
||||||
|
*/
|
||||||
|
#define ece_generic_processing(node) \
|
||||||
|
expression_tree_mutator((Node *) (node), eval_const_expressions_mutator, \
|
||||||
|
(void *) context)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check whether all arguments of the given node were reduced to Consts.
|
||||||
|
* By going directly to expression_tree_walker, contain_non_const_walker
|
||||||
|
* is not applied to the node itself, only to its children.
|
||||||
|
*/
|
||||||
|
#define ece_all_arguments_const(node) \
|
||||||
|
(!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL))
|
||||||
|
|
||||||
|
/* Generic macro for applying evaluate_expr */
|
||||||
|
#define ece_evaluate_expr(node) \
|
||||||
|
((Node *) evaluate_expr((Expr *) (node), \
|
||||||
|
exprType((Node *) (node)), \
|
||||||
|
exprTypmod((Node *) (node)), \
|
||||||
|
exprCollation((Node *) (node))))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recursive guts of eval_const_expressions/estimate_expression_value
|
||||||
|
*/
|
||||||
static Node *
|
static Node *
|
||||||
eval_const_expressions_mutator(Node *node,
|
eval_const_expressions_mutator(Node *node,
|
||||||
eval_const_expressions_context *context)
|
eval_const_expressions_context *context)
|
||||||
@ -2830,6 +2864,25 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
newexpr->location = expr->location;
|
newexpr->location = expr->location;
|
||||||
return (Node *) newexpr;
|
return (Node *) newexpr;
|
||||||
}
|
}
|
||||||
|
case T_ScalarArrayOpExpr:
|
||||||
|
{
|
||||||
|
ScalarArrayOpExpr *saop;
|
||||||
|
|
||||||
|
/* Copy the node and const-simplify its arguments */
|
||||||
|
saop = (ScalarArrayOpExpr *) ece_generic_processing(node);
|
||||||
|
|
||||||
|
/* Make sure we know underlying function */
|
||||||
|
set_sa_opfuncid(saop);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If all arguments are Consts, and it's a safe function, we
|
||||||
|
* can fold to a constant
|
||||||
|
*/
|
||||||
|
if (ece_all_arguments_const(saop) &&
|
||||||
|
ece_function_is_safe(saop->opfuncid, context))
|
||||||
|
return ece_evaluate_expr(saop);
|
||||||
|
return (Node *) saop;
|
||||||
|
}
|
||||||
case T_BoolExpr:
|
case T_BoolExpr:
|
||||||
{
|
{
|
||||||
BoolExpr *expr = (BoolExpr *) node;
|
BoolExpr *expr = (BoolExpr *) node;
|
||||||
@ -3054,47 +3107,24 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
}
|
}
|
||||||
case T_ArrayCoerceExpr:
|
case T_ArrayCoerceExpr:
|
||||||
{
|
{
|
||||||
ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
|
ArrayCoerceExpr *ac;
|
||||||
Expr *arg;
|
|
||||||
Expr *elemexpr;
|
/* Copy the node and const-simplify its arguments */
|
||||||
ArrayCoerceExpr *newexpr;
|
ac = (ArrayCoerceExpr *) ece_generic_processing(node);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reduce constants in the ArrayCoerceExpr's argument and
|
* If constant argument and the per-element expression is
|
||||||
* per-element expressions, then build a new ArrayCoerceExpr.
|
|
||||||
*/
|
|
||||||
arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
|
|
||||||
context);
|
|
||||||
elemexpr = (Expr *) eval_const_expressions_mutator((Node *) expr->elemexpr,
|
|
||||||
context);
|
|
||||||
|
|
||||||
newexpr = makeNode(ArrayCoerceExpr);
|
|
||||||
newexpr->arg = arg;
|
|
||||||
newexpr->elemexpr = elemexpr;
|
|
||||||
newexpr->resulttype = expr->resulttype;
|
|
||||||
newexpr->resulttypmod = expr->resulttypmod;
|
|
||||||
newexpr->resultcollid = expr->resultcollid;
|
|
||||||
newexpr->coerceformat = expr->coerceformat;
|
|
||||||
newexpr->location = expr->location;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If constant argument and per-element expression is
|
|
||||||
* immutable, we can simplify the whole thing to a constant.
|
* immutable, we can simplify the whole thing to a constant.
|
||||||
* Exception: although contain_mutable_functions considers
|
* Exception: although contain_mutable_functions considers
|
||||||
* CoerceToDomain immutable for historical reasons, let's not
|
* CoerceToDomain immutable for historical reasons, let's not
|
||||||
* do so here; this ensures coercion to an array-over-domain
|
* do so here; this ensures coercion to an array-over-domain
|
||||||
* does not apply the domain's constraints until runtime.
|
* does not apply the domain's constraints until runtime.
|
||||||
*/
|
*/
|
||||||
if (arg && IsA(arg, Const) &&
|
if (ac->arg && IsA(ac->arg, Const) &&
|
||||||
elemexpr && !IsA(elemexpr, CoerceToDomain) &&
|
ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
|
||||||
!contain_mutable_functions((Node *) elemexpr))
|
!contain_mutable_functions((Node *) ac->elemexpr))
|
||||||
return (Node *) evaluate_expr((Expr *) newexpr,
|
return ece_evaluate_expr(ac);
|
||||||
newexpr->resulttype,
|
return (Node *) ac;
|
||||||
newexpr->resulttypmod,
|
|
||||||
newexpr->resultcollid);
|
|
||||||
|
|
||||||
/* Else we must return the partially-simplified node */
|
|
||||||
return (Node *) newexpr;
|
|
||||||
}
|
}
|
||||||
case T_CollateExpr:
|
case T_CollateExpr:
|
||||||
{
|
{
|
||||||
@ -3286,41 +3316,22 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
else
|
else
|
||||||
return copyObject(node);
|
return copyObject(node);
|
||||||
}
|
}
|
||||||
|
case T_ArrayRef:
|
||||||
case T_ArrayExpr:
|
case T_ArrayExpr:
|
||||||
|
case T_RowExpr:
|
||||||
{
|
{
|
||||||
ArrayExpr *arrayexpr = (ArrayExpr *) node;
|
/*
|
||||||
ArrayExpr *newarray;
|
* Generic handling for node types whose own processing is
|
||||||
bool all_const = true;
|
* known to be immutable, and for which we need no smarts
|
||||||
List *newelems;
|
* beyond "simplify if all inputs are constants".
|
||||||
ListCell *element;
|
*/
|
||||||
|
|
||||||
newelems = NIL;
|
/* Copy the node and const-simplify its arguments */
|
||||||
foreach(element, arrayexpr->elements)
|
node = ece_generic_processing(node);
|
||||||
{
|
/* If all arguments are Consts, we can fold to a constant */
|
||||||
Node *e;
|
if (ece_all_arguments_const(node))
|
||||||
|
return ece_evaluate_expr(node);
|
||||||
e = eval_const_expressions_mutator((Node *) lfirst(element),
|
return node;
|
||||||
context);
|
|
||||||
if (!IsA(e, Const))
|
|
||||||
all_const = false;
|
|
||||||
newelems = lappend(newelems, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
newarray = makeNode(ArrayExpr);
|
|
||||||
newarray->array_typeid = arrayexpr->array_typeid;
|
|
||||||
newarray->array_collid = arrayexpr->array_collid;
|
|
||||||
newarray->element_typeid = arrayexpr->element_typeid;
|
|
||||||
newarray->elements = newelems;
|
|
||||||
newarray->multidims = arrayexpr->multidims;
|
|
||||||
newarray->location = arrayexpr->location;
|
|
||||||
|
|
||||||
if (all_const)
|
|
||||||
return (Node *) evaluate_expr((Expr *) newarray,
|
|
||||||
newarray->array_typeid,
|
|
||||||
exprTypmod(node),
|
|
||||||
newarray->array_collid);
|
|
||||||
|
|
||||||
return (Node *) newarray;
|
|
||||||
}
|
}
|
||||||
case T_CoalesceExpr:
|
case T_CoalesceExpr:
|
||||||
{
|
{
|
||||||
@ -3397,7 +3408,8 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
* simple Var. (This case won't be generated directly by the
|
* simple Var. (This case won't be generated directly by the
|
||||||
* parser, because ParseComplexProjection short-circuits it.
|
* parser, because ParseComplexProjection short-circuits it.
|
||||||
* But it can arise while simplifying functions.) Also, we
|
* But it can arise while simplifying functions.) Also, we
|
||||||
* can optimize field selection from a RowExpr construct.
|
* can optimize field selection from a RowExpr construct, or
|
||||||
|
* of course from a constant.
|
||||||
*
|
*
|
||||||
* However, replacing a whole-row Var in this way has a
|
* However, replacing a whole-row Var in this way has a
|
||||||
* pitfall: if we've already built the rel targetlist for the
|
* pitfall: if we've already built the rel targetlist for the
|
||||||
@ -3412,6 +3424,8 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
* We must also check that the declared type of the field is
|
* We must also check that the declared type of the field is
|
||||||
* still the same as when the FieldSelect was created --- this
|
* still the same as when the FieldSelect was created --- this
|
||||||
* can change if someone did ALTER COLUMN TYPE on the rowtype.
|
* can change if someone did ALTER COLUMN TYPE on the rowtype.
|
||||||
|
* If it isn't, we skip the optimization; the case will
|
||||||
|
* probably fail at runtime, but that's not our problem here.
|
||||||
*/
|
*/
|
||||||
FieldSelect *fselect = (FieldSelect *) node;
|
FieldSelect *fselect = (FieldSelect *) node;
|
||||||
FieldSelect *newfselect;
|
FieldSelect *newfselect;
|
||||||
@ -3462,6 +3476,17 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
newfselect->resulttype = fselect->resulttype;
|
newfselect->resulttype = fselect->resulttype;
|
||||||
newfselect->resulttypmod = fselect->resulttypmod;
|
newfselect->resulttypmod = fselect->resulttypmod;
|
||||||
newfselect->resultcollid = fselect->resultcollid;
|
newfselect->resultcollid = fselect->resultcollid;
|
||||||
|
if (arg && IsA(arg, Const))
|
||||||
|
{
|
||||||
|
Const *con = (Const *) arg;
|
||||||
|
|
||||||
|
if (rowtype_field_matches(con->consttype,
|
||||||
|
newfselect->fieldnum,
|
||||||
|
newfselect->resulttype,
|
||||||
|
newfselect->resulttypmod,
|
||||||
|
newfselect->resultcollid))
|
||||||
|
return ece_evaluate_expr(newfselect);
|
||||||
|
}
|
||||||
return (Node *) newfselect;
|
return (Node *) newfselect;
|
||||||
}
|
}
|
||||||
case T_NullTest:
|
case T_NullTest:
|
||||||
@ -3557,6 +3582,13 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
}
|
}
|
||||||
case T_BooleanTest:
|
case T_BooleanTest:
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* This case could be folded into the generic handling used
|
||||||
|
* for ArrayRef etc. But because the simplification logic is
|
||||||
|
* so trivial, applying evaluate_expr() to perform it would be
|
||||||
|
* a heavy overhead. BooleanTest is probably common enough to
|
||||||
|
* justify keeping this bespoke implementation.
|
||||||
|
*/
|
||||||
BooleanTest *btest = (BooleanTest *) node;
|
BooleanTest *btest = (BooleanTest *) node;
|
||||||
BooleanTest *newbtest;
|
BooleanTest *newbtest;
|
||||||
Node *arg;
|
Node *arg;
|
||||||
@ -3630,14 +3662,57 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For any node type not handled above, we recurse using
|
* For any node type not handled above, copy the node unchanged but
|
||||||
* expression_tree_mutator, which will copy the node unchanged but try to
|
* const-simplify its subexpressions. This is the correct thing for node
|
||||||
* simplify its arguments (if any) using this routine. For example: we
|
* types whose behavior might change between planning and execution, such
|
||||||
* cannot eliminate an ArrayRef node, but we might be able to simplify
|
* as CoerceToDomain. It's also a safe default for new node types not
|
||||||
* constant expressions in its subscripts.
|
* known to this routine.
|
||||||
*/
|
*/
|
||||||
return expression_tree_mutator(node, eval_const_expressions_mutator,
|
return ece_generic_processing(node);
|
||||||
(void *) context);
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Subroutine for eval_const_expressions: check for non-Const nodes.
|
||||||
|
*
|
||||||
|
* We can abort recursion immediately on finding a non-Const node. This is
|
||||||
|
* critical for performance, else eval_const_expressions_mutator would take
|
||||||
|
* O(N^2) time on non-simplifiable trees. However, we do need to descend
|
||||||
|
* into List nodes since expression_tree_walker sometimes invokes the walker
|
||||||
|
* function directly on List subtrees.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
contain_non_const_walker(Node *node, void *context)
|
||||||
|
{
|
||||||
|
if (node == NULL)
|
||||||
|
return false;
|
||||||
|
if (IsA(node, Const))
|
||||||
|
return false;
|
||||||
|
if (IsA(node, List))
|
||||||
|
return expression_tree_walker(node, contain_non_const_walker, context);
|
||||||
|
/* Otherwise, abort the tree traversal and return true */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Subroutine for eval_const_expressions: check if a function is OK to evaluate
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
|
||||||
|
{
|
||||||
|
char provolatile = func_volatile(funcid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ordinarily we are only allowed to simplify immutable functions. But for
|
||||||
|
* purposes of estimation, we consider it okay to simplify functions that
|
||||||
|
* are merely stable; the risk that the result might change from planning
|
||||||
|
* time to execution time is worth taking in preference to not being able
|
||||||
|
* to estimate the value at all.
|
||||||
|
*/
|
||||||
|
if (provolatile == PROVOLATILE_IMMUTABLE)
|
||||||
|
return true;
|
||||||
|
if (context->estimate && provolatile == PROVOLATILE_STABLE)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -307,10 +307,10 @@ ERROR: cannot compare dissimilar column types bigint and integer at record colu
|
|||||||
explain (costs off)
|
explain (costs off)
|
||||||
select * from int8_tbl i8
|
select * from int8_tbl i8
|
||||||
where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
|
where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
|
||||||
QUERY PLAN
|
QUERY PLAN
|
||||||
-----------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
Seq Scan on int8_tbl i8
|
Seq Scan on int8_tbl i8
|
||||||
Filter: (i8.* = ANY (ARRAY[ROW('123'::bigint, '456'::bigint)::int8_tbl, '(4567890123456789,123)'::int8_tbl]))
|
Filter: (i8.* = ANY ('{"(123,456)","(4567890123456789,123)"}'::int8_tbl[]))
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
select * from int8_tbl i8
|
select * from int8_tbl i8
|
||||||
|
Loading…
x
Reference in New Issue
Block a user