diff options
Diffstat (limited to 'src/backend/optimizer')
| -rw-r--r-- | src/backend/optimizer/plan/planner.c | 17 | ||||
| -rw-r--r-- | src/backend/optimizer/prep/prepjointree.c | 356 | ||||
| -rw-r--r-- | src/backend/optimizer/util/clauses.c | 41 |
3 files changed, 271 insertions, 143 deletions
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 36fefd96a4..8f51f59f8a 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -659,11 +659,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, pull_up_sublinks(root); /* - * Scan the rangetable for set-returning functions, and inline them if - * possible (producing subqueries that might get pulled up next). - * Recursion issues here are handled in the same way as for SubLinks. + * Scan the rangetable for function RTEs, do const-simplification on them, + * and then inline them if possible (producing subqueries that might get + * pulled up next). Recursion issues here are handled in the same way as + * for SubLinks. */ - inline_set_returning_functions(root); + preprocess_function_rtes(root); /* * Check to see if any subqueries in the jointree can be merged into this @@ -1071,7 +1072,9 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) expr = flatten_join_alias_vars(root->parse, expr); /* - * Simplify constant expressions. + * Simplify constant expressions. For function RTEs, this was already + * done by preprocess_function_rtes ... but we have to do it again if the + * RTE is LATERAL and might have contained join alias variables. * * Note: an essential effect of this is to convert named-argument function * calls to positional notation and insert the current actual values of @@ -1085,7 +1088,9 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) * careful to maintain AND/OR flatness --- that is, do not generate a tree * with AND directly under AND, nor OR directly under OR. */ - expr = eval_const_expressions(root, expr); + if (!(kind == EXPRKIND_RTFUNC || + (kind == EXPRKIND_RTFUNC_LATERAL && !root->hasJoinRTEs))) + expr = eval_const_expressions(root, expr); /* * If it's a qual or havingQual, canonicalize it. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 4fbc03fe54..ccb32530ad 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -6,7 +6,7 @@ * NOTE: the intended sequence for invoking these operations is * replace_empty_jointree * pull_up_sublinks - * inline_set_returning_functions + * preprocess_function_rtes * pull_up_subqueries * flatten_simple_union_all * do expression preprocessing (including flattening JOIN alias vars) @@ -86,12 +86,20 @@ static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte, static Node *pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte); static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte); +static Node *pull_up_constant_function(PlannerInfo *root, Node *jtnode, + RangeTblEntry *rte, + JoinExpr *lowest_nulling_outer_join, + AppendRelInfo *containing_appendrel); static bool is_simple_union_all(Query *subquery); static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, List *colTypes); static bool is_safe_append_member(Query *subquery); static bool jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted, Relids safe_upper_varnos); +static void perform_pullup_replace_vars(PlannerInfo *root, + pullup_replace_vars_context *rvcontext, + JoinExpr *lowest_nulling_outer_join, + AppendRelInfo *containing_appendrel); static void replace_vars_in_jointree(Node *jtnode, pullup_replace_vars_context *context, JoinExpr *lowest_nulling_outer_join); @@ -597,8 +605,9 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, } /* - * inline_set_returning_functions - * Attempt to "inline" set-returning functions in the FROM clause. + * preprocess_function_rtes + * Constant-simplify any FUNCTION RTEs in the FROM clause, and then + * attempt to "inline" any that are set-returning functions. * * If an RTE_FUNCTION rtable entry invokes a set-returning function that * contains just a simple SELECT, we can convert the rtable entry to an @@ -611,11 +620,18 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, * obtained via inlining. However, we do it after pull_up_sublinks * so that we can inline any functions used in SubLink subselects. * + * The reason for applying const-simplification at this stage is that + * (a) we'd need to do it anyway to inline a SRF, and (b) by doing it now, + * we can be sure that pull_up_constant_function() will see constants + * if there are constants to be seen. This approach also guarantees + * that every FUNCTION RTE has been const-simplified, allowing planner.c's + * preprocess_expression() to skip doing it again. + * * Like most of the planner, this feels free to scribble on its input data * structure. */ void -inline_set_returning_functions(PlannerInfo *root) +preprocess_function_rtes(PlannerInfo *root) { ListCell *rt; @@ -627,6 +643,10 @@ inline_set_returning_functions(PlannerInfo *root) { Query *funcquery; + /* Apply const-simplification */ + rte->functions = (List *) + eval_const_expressions(root, (Node *) rte->functions); + /* Check safety of expansion, and expand if possible */ funcquery = inline_set_returning_function(root, rte); if (funcquery) @@ -754,6 +774,14 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, is_simple_values(root, rte)) return pull_up_simple_values(root, jtnode, rte); + /* + * Or perhaps it's a FUNCTION RTE that we could inline? + */ + if (rte->rtekind == RTE_FUNCTION) + return pull_up_constant_function(root, jtnode, rte, + lowest_nulling_outer_join, + containing_appendrel); + /* Otherwise, do nothing at this node. */ } else if (IsA(jtnode, FromExpr)) @@ -917,9 +945,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, pull_up_sublinks(subroot); /* - * Similarly, inline any set-returning functions in its rangetable. + * Similarly, preprocess its function RTEs to inline any set-returning + * functions in its rangetable. */ - inline_set_returning_functions(subroot); + preprocess_function_rtes(subroot); /* * Recursively pull up the subquery's subqueries, so that @@ -1047,72 +1076,11 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, /* * Replace all of the top query's references to the subquery's outputs * with copies of the adjusted subtlist items, being careful not to - * replace any of the jointree structure. (This'd be a lot cleaner if we - * could use query_tree_mutator.) We have to use PHVs in the targetList, - * returningList, and havingQual, since those are certainly above any - * outer join. replace_vars_in_jointree tracks its location in the - * jointree and uses PHVs or not appropriately. + * replace any of the jointree structure. */ - parse->targetList = (List *) - pullup_replace_vars((Node *) parse->targetList, &rvcontext); - parse->returningList = (List *) - pullup_replace_vars((Node *) parse->returningList, &rvcontext); - if (parse->onConflict) - { - parse->onConflict->onConflictSet = (List *) - pullup_replace_vars((Node *) parse->onConflict->onConflictSet, - &rvcontext); - parse->onConflict->onConflictWhere = - pullup_replace_vars(parse->onConflict->onConflictWhere, - &rvcontext); - - /* - * We assume ON CONFLICT's arbiterElems, arbiterWhere, exclRelTlist - * can't contain any references to a subquery - */ - } - replace_vars_in_jointree((Node *) parse->jointree, &rvcontext, - lowest_nulling_outer_join); - Assert(parse->setOperations == NULL); - parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext); - - /* - * Replace references in the translated_vars lists of appendrels. When - * pulling up an appendrel member, we do not need PHVs in the list of the - * parent appendrel --- there isn't any outer join between. Elsewhere, use - * PHVs for safety. (This analysis could be made tighter but it seems - * unlikely to be worth much trouble.) - */ - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - bool save_need_phvs = rvcontext.need_phvs; - - if (appinfo == containing_appendrel) - rvcontext.need_phvs = false; - appinfo->translated_vars = (List *) - pullup_replace_vars((Node *) appinfo->translated_vars, &rvcontext); - rvcontext.need_phvs = save_need_phvs; - } - - /* - * Replace references in the joinaliasvars lists of join RTEs. - * - * You might think that we could avoid using PHVs for alias vars of joins - * below lowest_nulling_outer_join, but that doesn't work because the - * alias vars could be referenced above that join; we need the PHVs to be - * present in such references after the alias vars get flattened. (It - * might be worth trying to be smarter here, someday.) - */ - foreach(lc, parse->rtable) - { - RangeTblEntry *otherrte = (RangeTblEntry *) lfirst(lc); - - if (otherrte->rtekind == RTE_JOIN) - otherrte->joinaliasvars = (List *) - pullup_replace_vars((Node *) otherrte->joinaliasvars, - &rvcontext); - } + perform_pullup_replace_vars(root, &rvcontext, + lowest_nulling_outer_join, + containing_appendrel); /* * If the subquery had a LATERAL marker, propagate that to any of its @@ -1608,39 +1576,16 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) /* * Replace all of the top query's references to the RTE's outputs with * copies of the adjusted VALUES expressions, being careful not to replace - * any of the jointree structure. (This'd be a lot cleaner if we could use - * query_tree_mutator.) Much of this should be no-ops in the dummy Query - * that surrounds a VALUES RTE, but it's not enough code to be worth - * removing. + * any of the jointree structure. We can assume there's no outer joins or + * appendrels in the dummy Query that surrounds a VALUES RTE. */ - parse->targetList = (List *) - pullup_replace_vars((Node *) parse->targetList, &rvcontext); - parse->returningList = (List *) - pullup_replace_vars((Node *) parse->returningList, &rvcontext); - if (parse->onConflict) - { - parse->onConflict->onConflictSet = (List *) - pullup_replace_vars((Node *) parse->onConflict->onConflictSet, - &rvcontext); - parse->onConflict->onConflictWhere = - pullup_replace_vars(parse->onConflict->onConflictWhere, - &rvcontext); - - /* - * We assume ON CONFLICT's arbiterElems, arbiterWhere, exclRelTlist - * can't contain any references to a subquery - */ - } - replace_vars_in_jointree((Node *) parse->jointree, &rvcontext, NULL); - Assert(parse->setOperations == NULL); - parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext); + perform_pullup_replace_vars(root, &rvcontext, NULL, NULL); /* - * There should be no appendrels to fix, nor any join alias Vars, nor any - * outer joins and hence no PlaceHolderVars. + * There should be no appendrels to fix, nor any outer joins and hence no + * PlaceHolderVars. */ Assert(root->append_rel_list == NIL); - Assert(list_length(parse->rtable) == 1); Assert(root->join_info_list == NIL); Assert(root->placeholder_list == NIL); @@ -1713,6 +1658,125 @@ is_simple_values(PlannerInfo *root, RangeTblEntry *rte) } /* + * pull_up_constant_function + * Pull up an RTE_FUNCTION expression that was simplified to a constant. + * + * jtnode is a RangeTblRef that has been identified as a FUNCTION RTE by + * pull_up_subqueries. If its expression is just a Const, hoist that value + * up into the parent query, and replace the RTE_FUNCTION with RTE_RESULT. + * + * In principle we could pull up any immutable expression, but we don't. + * That might result in multiple evaluations of the expression, which could + * be costly if it's not just a Const. Also, the main value of this is + * to let the constant participate in further const-folding, and of course + * that won't happen for a non-Const. + * + * The pulled-up value might need to be wrapped in a PlaceHolderVar if the + * RTE is below an outer join or is part of an appendrel; the extra + * parameters show whether that's needed. + */ +static Node * +pull_up_constant_function(PlannerInfo *root, Node *jtnode, + RangeTblEntry *rte, + JoinExpr *lowest_nulling_outer_join, + AppendRelInfo *containing_appendrel) +{ + Query *parse = root->parse; + RangeTblFunction *rtf; + pullup_replace_vars_context rvcontext; + + /* Fail if the RTE has ORDINALITY - we don't implement that here. */ + if (rte->funcordinality) + return jtnode; + + /* Fail if RTE isn't a single, simple Const expr */ + if (list_length(rte->functions) != 1) + return jtnode; + rtf = linitial_node(RangeTblFunction, rte->functions); + if (!IsA(rtf->funcexpr, Const)) + return jtnode; + + /* Create context for applying pullup_replace_vars */ + rvcontext.root = root; + rvcontext.targetlist = list_make1(makeTargetEntry((Expr *) rtf->funcexpr, + 1, /* resno */ + NULL, /* resname */ + false)); /* resjunk */ + rvcontext.target_rte = rte; + + /* + * Since this function was reduced to a Const, it doesn't contain any + * lateral references, even if it's marked as LATERAL. This means we + * don't need to fill relids. + */ + rvcontext.relids = NULL; + + rvcontext.outer_hasSubLinks = &parse->hasSubLinks; + rvcontext.varno = ((RangeTblRef *) jtnode)->rtindex; + /* these flags will be set below, if needed */ + rvcontext.need_phvs = false; + rvcontext.wrap_non_vars = false; + /* initialize cache array with indexes 0 .. length(tlist) */ + rvcontext.rv_cache = palloc0((list_length(rvcontext.targetlist) + 1) * + sizeof(Node *)); + + /* + * If we are under an outer join then non-nullable items and lateral + * references may have to be turned into PlaceHolderVars. + */ + if (lowest_nulling_outer_join != NULL) + rvcontext.need_phvs = true; + + /* + * If we are dealing with an appendrel member then anything that's not a + * simple Var has to be turned into a PlaceHolderVar. (See comments in + * pull_up_simple_subquery().) + */ + if (containing_appendrel != NULL) + { + rvcontext.need_phvs = true; + rvcontext.wrap_non_vars = true; + } + + /* + * If the parent query uses grouping sets, we need a PlaceHolderVar for + * anything that's not a simple Var. + */ + if (parse->groupingSets) + { + rvcontext.need_phvs = true; + rvcontext.wrap_non_vars = true; + } + + /* + * Replace all of the top query's references to the RTE's output with + * copies of the funcexpr, being careful not to replace any of the + * jointree structure. + */ + perform_pullup_replace_vars(root, &rvcontext, + lowest_nulling_outer_join, + containing_appendrel); + + /* + * We don't need to bother with changing PlaceHolderVars in the parent + * query. Their references to the RT index are still good for now, and + * will get removed later if we're able to drop the RTE_RESULT. + */ + + /* + * Convert the RTE to be RTE_RESULT type, signifying that we don't need to + * scan it anymore, and zero out RTE_FUNCTION-specific fields. + */ + rte->rtekind = RTE_RESULT; + rte->functions = NIL; + + /* + * We can reuse the RangeTblRef node. + */ + return jtnode; +} + +/* * is_simple_union_all * Check a subquery to see if it's a simple UNION ALL. * @@ -1901,9 +1965,97 @@ jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted, } /* - * Helper routine for pull_up_subqueries: do pullup_replace_vars on every - * expression in the jointree, without changing the jointree structure itself. - * Ugly, but there's no other way... + * Perform pullup_replace_vars everyplace it's needed in the query tree. + * + * Caller has already filled *rvcontext with data describing what to + * substitute for Vars referencing the target subquery. In addition + * we need the identity of the lowest outer join that can null the + * target subquery, and its containing appendrel if any. + */ +static void +perform_pullup_replace_vars(PlannerInfo *root, + pullup_replace_vars_context *rvcontext, + JoinExpr *lowest_nulling_outer_join, + AppendRelInfo *containing_appendrel) +{ + Query *parse = root->parse; + ListCell *lc; + + /* + * Replace all of the top query's references to the subquery's outputs + * with copies of the adjusted subtlist items, being careful not to + * replace any of the jointree structure. (This'd be a lot cleaner if we + * could use query_tree_mutator.) We have to use PHVs in the targetList, + * returningList, and havingQual, since those are certainly above any + * outer join. replace_vars_in_jointree tracks its location in the + * jointree and uses PHVs or not appropriately. + */ + parse->targetList = (List *) + pullup_replace_vars((Node *) parse->targetList, rvcontext); + parse->returningList = (List *) + pullup_replace_vars((Node *) parse->returningList, rvcontext); + if (parse->onConflict) + { + parse->onConflict->onConflictSet = (List *) + pullup_replace_vars((Node *) parse->onConflict->onConflictSet, + rvcontext); + parse->onConflict->onConflictWhere = + pullup_replace_vars(parse->onConflict->onConflictWhere, + rvcontext); + + /* + * We assume ON CONFLICT's arbiterElems, arbiterWhere, exclRelTlist + * can't contain any references to a subquery. + */ + } + replace_vars_in_jointree((Node *) parse->jointree, rvcontext, + lowest_nulling_outer_join); + Assert(parse->setOperations == NULL); + parse->havingQual = pullup_replace_vars(parse->havingQual, rvcontext); + + /* + * Replace references in the translated_vars lists of appendrels. When + * pulling up an appendrel member, we do not need PHVs in the list of the + * parent appendrel --- there isn't any outer join between. Elsewhere, + * use PHVs for safety. (This analysis could be made tighter but it seems + * unlikely to be worth much trouble.) + */ + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); + bool save_need_phvs = rvcontext->need_phvs; + + if (appinfo == containing_appendrel) + rvcontext->need_phvs = false; + appinfo->translated_vars = (List *) + pullup_replace_vars((Node *) appinfo->translated_vars, rvcontext); + rvcontext->need_phvs = save_need_phvs; + } + + /* + * Replace references in the joinaliasvars lists of join RTEs. + * + * You might think that we could avoid using PHVs for alias vars of joins + * below lowest_nulling_outer_join, but that doesn't work because the + * alias vars could be referenced above that join; we need the PHVs to be + * present in such references after the alias vars get flattened. (It + * might be worth trying to be smarter here, someday.) + */ + foreach(lc, parse->rtable) + { + RangeTblEntry *otherrte = (RangeTblEntry *) lfirst(lc); + + if (otherrte->rtekind == RTE_JOIN) + otherrte->joinaliasvars = (List *) + pullup_replace_vars((Node *) otherrte->joinaliasvars, + rvcontext); + } +} + +/* + * Helper routine for perform_pullup_replace_vars: do pullup_replace_vars on + * every expression in the jointree, without changing the jointree structure + * itself. Ugly, but there's no other way... * * If we are at or below lowest_nulling_outer_join, we can suppress use of * PlaceHolderVars wrapped around the replacement expressions. diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 99dbf8da18..7ad9d9ab79 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4870,6 +4870,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, * set-returning SQL function that can safely be inlined, expand the function * and return the substitute Query structure. Otherwise, return NULL. * + * We assume that the RTE's expression has already been put through + * eval_const_expressions(), which among other things will take care of + * default arguments and named-argument notation. + * * This has a good deal of similarity to inline_function(), but that's * for the non-set-returning case, and there are enough differences to * justify separate functions. @@ -4888,7 +4892,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) bool modifyTargetList; MemoryContext oldcxt; MemoryContext mycxt; - List *saveInvalItems; inline_error_callback_arg callback_arg; ErrorContextCallback sqlerrcontext; SQLFunctionParseInfoPtr pinfo; @@ -4966,7 +4969,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) * sharing the snapshot of the calling query. We also disallow returning * SETOF VOID, because inlining would result in exposing the actual result * of the function's last SELECT, which should not happen in that case. - * (Rechecking prokind and proretset is just paranoia.) + * (Rechecking prokind, proretset, and pronargs is just paranoia.) */ if (funcform->prolang != SQLlanguageId || funcform->prokind != PROKIND_FUNCTION || @@ -4975,6 +4978,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) funcform->prorettype == VOIDOID || funcform->prosecdef || !funcform->proretset || + list_length(fexpr->args) != funcform->pronargs || !heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL)) { ReleaseSysCache(func_tuple); @@ -4990,16 +4994,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(mycxt); - /* - * When we call eval_const_expressions below, it might try to add items to - * root->glob->invalItems. Since it is running in the temp context, those - * items will be in that context, and will need to be copied out if we're - * successful. Temporarily reset the list so that we can keep those items - * separate from the pre-existing list contents. - */ - saveInvalItems = root->glob->invalItems; - root->glob->invalItems = NIL; - /* Fetch the function body */ tmp = SysCacheGetAttr(PROCOID, func_tuple, @@ -5022,24 +5016,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) error_context_stack = &sqlerrcontext; /* - * Run eval_const_expressions on the function call. This is necessary to - * ensure that named-argument notation is converted to positional notation - * and any default arguments are inserted. It's a bit of overkill for the - * arguments, since they'll get processed again later, but no harm will be - * done. - */ - fexpr = (FuncExpr *) eval_const_expressions(root, (Node *) fexpr); - - /* It should still be a call of the same function, but let's check */ - if (!IsA(fexpr, FuncExpr) || - fexpr->funcid != func_oid) - goto fail; - - /* Arg list length should now match the function */ - if (list_length(fexpr->args) != funcform->pronargs) - goto fail; - - /* * Set up to handle parameters while parsing the function body. We can * use the FuncExpr just created as the input for * prepare_sql_fn_parse_info. @@ -5129,10 +5105,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) querytree = copyObject(querytree); - /* copy up any new invalItems, too */ - root->glob->invalItems = list_concat(saveInvalItems, - copyObject(root->glob->invalItems)); - MemoryContextDelete(mycxt); error_context_stack = sqlerrcontext.previous; ReleaseSysCache(func_tuple); @@ -5153,7 +5125,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) /* Here if func is not inlinable: release temp memory and return NULL */ fail: MemoryContextSwitchTo(oldcxt); - root->glob->invalItems = saveInvalItems; MemoryContextDelete(mycxt); error_context_stack = sqlerrcontext.previous; ReleaseSysCache(func_tuple); |
