diff options
Diffstat (limited to 'src/backend/optimizer')
| -rw-r--r-- | src/backend/optimizer/plan/planmain.c | 35 | ||||
| -rw-r--r-- | src/backend/optimizer/plan/planner.c | 39 | ||||
| -rw-r--r-- | src/backend/optimizer/util/clauses.c | 421 |
3 files changed, 466 insertions, 29 deletions
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index cab9efc1524..3cc3f0a6940 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.44 1999/09/13 00:17:25 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.45 1999/09/26 02:28:27 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -66,24 +66,41 @@ query_planner(Query *root, List *level_tlist; Plan *subplan; + /* + * Simplify constant expressions in both targetlist and qual. + * + * Note that at this point the qual has not yet been converted to + * implicit-AND form, so we can apply eval_const_expressions directly. + * Also note that we need to do this before SS_process_sublinks, + * because that routine inserts bogus "Const" nodes. + */ + tlist = (List *) eval_const_expressions((Node *) tlist); + qual = (List *) eval_const_expressions((Node *) qual); + + /* + * Canonicalize the qual, and convert it to implicit-AND format. + */ + qual = canonicalize_qual((Expr *) qual, true); +#ifdef OPTIMIZER_DEBUG + printf("After canonicalize_qual()\n"); + pprint(qual); +#endif + + /* Replace uplevel vars with Param nodes */ if (PlannerQueryLevel > 1) { - /* should copy be made ? */ tlist = (List *) SS_replace_correlation_vars((Node *) tlist); qual = (List *) SS_replace_correlation_vars((Node *) qual); } + /* Expand SubLinks to SubPlans */ if (root->hasSubLinks) qual = (List *) SS_process_sublinks((Node *) qual); - qual = canonicalize_qual((Expr *) qual, true); -#ifdef OPTIMIZER_DEBUG - printf("After canonicalize_qual()\n"); - pprint(qual); -#endif - /* * Pull out any non-variable qualifications so these can be put in the - * topmost result node. + * topmost result node. (Any *really* non-variable quals will probably + * have been optimized away by eval_const_expressions(). What we're + * looking for here is quals that depend only on outer-level vars...) */ qual = pull_constant_clauses(qual, &constant_qual); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 32a5bb52cda..d78a650c05d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.68 1999/09/18 19:07:00 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.69 1999/09/26 02:28:27 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -301,7 +301,28 @@ union_planner(Query *parse) */ if (parse->havingQual) { - List *ql; + /*-------------------- + * Require the havingQual to contain at least one aggregate function + * (else it could have been done as a WHERE constraint). This check + * used to be much stricter, requiring an aggregate in each clause of + * the CNF-ified qual. However, that's probably overly anal-retentive. + * We now do it first so that we will not complain if there is an + * aggregate but it gets optimized away by eval_const_expressions(). + * The agg itself is never const, of course, but consider + * SELECT ... HAVING xyz OR (COUNT(*) > 1) + * where xyz reduces to constant true in a particular query. + * We probably should not refuse this query. + *-------------------- + */ + if (pull_agg_clause(parse->havingQual) == NIL) + elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); + + /* Simplify constant expressions in havingQual */ + parse->havingQual = eval_const_expressions(parse->havingQual); + + /* Convert the havingQual to implicit-AND normal form */ + parse->havingQual = (Node *) + canonicalize_qual((Expr *) parse->havingQual, true); /* Replace uplevel Vars with Params */ if (PlannerQueryLevel > 1) @@ -323,20 +344,6 @@ union_planner(Query *parse) parse->targetList)) elog(ERROR, "Sub-SELECT in HAVING clause must use only GROUPed attributes from outer SELECT"); } - - /* convert the havingQual to implicit-AND normal form */ - parse->havingQual = (Node *) - canonicalize_qual((Expr *) parse->havingQual, true); - - /* - * Require an aggregate function to appear in each clause of the - * havingQual (else it could have been done as a WHERE constraint). - */ - foreach(ql, (List *) parse->havingQual) - { - if (pull_agg_clause(lfirst(ql)) == NIL) - elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); - } } /* diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 876a0dafb72..7f77ae78a66 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.51 1999/09/09 02:35:53 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.52 1999/09/26 02:28:33 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -19,6 +19,9 @@ #include "postgres.h" #include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "executor/executor.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" @@ -26,7 +29,9 @@ #include "optimizer/internal.h" #include "optimizer/tlist.h" #include "optimizer/var.h" +#include "parser/parse_type.h" #include "utils/lsyscache.h" +#include "utils/syscache.h" typedef struct { @@ -38,6 +43,7 @@ static bool pull_agg_clause_walker(Node *node, List **listptr); static bool check_subplans_for_ungrouped_vars_walker(Node *node, check_subplans_for_ungrouped_vars_context *context); static int is_single_func(Node *node); +static Node *eval_const_expressions_mutator (Node *node, void *context); Expr * @@ -211,7 +217,7 @@ make_orclause(List *orclauses) { Expr *expr = makeNode(Expr); - expr->typeOid = InvalidOid; /* assume type checking done */ + expr->typeOid = BOOLOID; expr->opType = OR_EXPR; expr->oper = NULL; expr->args = orclauses; @@ -247,7 +253,7 @@ make_notclause(Expr *notclause) { Expr *expr = makeNode(Expr); - expr->typeOid = InvalidOid; /* assume type checking done */ + expr->typeOid = BOOLOID; expr->opType = NOT_EXPR; expr->oper = NULL; expr->args = lcons(notclause, NIL); @@ -296,7 +302,7 @@ make_andclause(List *andclauses) { Expr *expr = makeNode(Expr); - expr->typeOid = InvalidOid; /* assume type checking done */ + expr->typeOid = BOOLOID; expr->opType = AND_EXPR; expr->oper = NULL; expr->args = andclauses; @@ -762,6 +768,413 @@ CommuteClause(Expr *clause) } +/*-------------------- + * eval_const_expressions + * + * Reduce any recognizably constant subexpressions of the given + * expression tree, for example "2 + 2" => "4". More interestingly, + * we can reduce certain boolean expressions even when they contain + * non-constant subexpressions: "x OR true" => "true" no matter what + * the subexpression x is. (XXX We assume that no such subexpression + * will have important side-effects, which is not necessarily a good + * assumption in the presence of user-defined functions; do we need a + * pg_proc flag that prevents discarding the execution of a function?) + * + * We do understand that certain functions may deliver non-constant + * results even with constant inputs, "nextval()" being the classic + * example. Functions that are not marked "proiscachable" in pg_proc + * will not be pre-evaluated here, although we will reduce their + * arguments as far as possible. Functions that are the arguments + * of Iter nodes are also not evaluated. + * + * We assume that the tree has already been type-checked and contains + * only operators and functions that are reasonable to try to execute. + * + * This routine should be invoked before converting sublinks to subplans + * (subselect.c's SS_process_sublinks()). The converted form contains + * bogus "Const" nodes that are actually placeholders where the executor + * will insert values from the inner plan, and obviously we mustn't try + * to reduce the expression as though these were really constants. + * As a safeguard, if we happen to find an already-converted SubPlan node, + * we will return it unchanged rather than recursing into it. + *-------------------- + */ +Node * +eval_const_expressions(Node *node) +{ + /* no context or special setup needed, so away we go... */ + return eval_const_expressions_mutator(node, NULL); +} + +/* note that pg_type.h hardwires size of bool as 1 ... duplicate it */ +#define MAKEBOOLCONST(val,isnull) \ + ((Node *) makeConst(BOOLOID, 1, (Datum) (val), \ + (isnull), true, false, false)) + +static Node * +eval_const_expressions_mutator (Node *node, void *context) +{ + if (node == NULL) + return NULL; + if (IsA(node, Expr)) + { + Expr *expr = (Expr *) node; + List *args; + Const *const_input; + Expr *newexpr; + + /* + * Reduce constants in the Expr's arguments. We know args is + * either NIL or a List node, so we can call expression_tree_mutator + * directly rather than recursing to self. + */ + args = (List *) expression_tree_mutator((Node *) expr->args, + eval_const_expressions_mutator, + (void *) context); + + switch (expr->opType) + { + case OP_EXPR: + case FUNC_EXPR: + { + /* + * For an operator or function, we cannot simplify + * unless all the inputs are constants. (XXX possible + * future improvement: if the op/func is strict and + * at least one input is NULL, we could simplify to NULL. + * But we do not currently have any way to know if the + * op/func is strict or not. For now, a NULL input is + * treated the same as any other constant node.) + */ + bool args_all_const = true; + List *arg; + Oid funcid; + Oid result_typeid; + HeapTuple func_tuple; + Form_pg_proc funcform; + Type resultType; + Datum const_val; + bool const_is_null; + bool isDone; + + foreach(arg, args) + { + if (! IsA(lfirst(arg), Const)) + { + args_all_const = false; + break; + } + } + if (! args_all_const) + break; + /* + * Get the function procedure's OID and look to see + * whether it is marked proiscachable. + */ + if (expr->opType == OP_EXPR) + { + Oper *oper = (Oper *) expr->oper; + + replace_opid(oper); + funcid = oper->opid; + result_typeid = oper->opresulttype; + } + else + { + Func *func = (Func *) expr->oper; + + funcid = func->funcid; + result_typeid = func->functype; + } + /* Someday lsyscache.c might provide a function for this */ + func_tuple = SearchSysCacheTuple(PROOID, + ObjectIdGetDatum(funcid), + 0, 0, 0); + if (!HeapTupleIsValid(func_tuple)) + elog(ERROR, "Function OID %u does not exist", funcid); + funcform = (Form_pg_proc) GETSTRUCT(func_tuple); + if (! funcform->proiscachable) + break; + /* + * Also check to make sure it doesn't return a set. + * + * XXX would it be better to take the result type from the + * pg_proc tuple, rather than the Oper or Func node? + */ + if (funcform->proretset) + break; + /* + * OK, looks like we can simplify this operator/function. + * We use the executor's routine ExecEvalExpr() to avoid + * duplication of code and ensure we get the same result + * as the executor would get. + * + * The only setup needed here is the replace_opid() + * that we already did for the OP_EXPR case. + * + * It is OK to pass econtext = NULL because none of the + * ExecEvalExpr() code used in this situation will use + * econtext. That might seem fortuitous, but it's not + * so unreasonable --- a constant expression does not + * depend on context, by definition, n'est ce pas? + */ + const_val = ExecEvalExpr((Node *) expr, NULL, + &const_is_null, &isDone); + Assert(isDone); /* if this isn't set, we blew it... */ + /* + * Make the constant result node. + */ + resultType = typeidType(result_typeid); + return (Node *) makeConst(result_typeid, typeLen(resultType), + const_val, const_is_null, + typeByVal(resultType), + false, false); + } + case OR_EXPR: + { + /* + * OR arguments are handled as follows: + * non constant: keep + * FALSE: drop (does not affect result) + * TRUE: force result to TRUE + * NULL: keep only one + * We keep one NULL input because ExecEvalOr returns + * NULL when no input is TRUE and at least one is NULL. + */ + List *newargs = NIL; + List *arg; + bool haveNull = false; + bool forceTrue = false; + + foreach(arg, args) + { + if (! IsA(lfirst(arg), Const)) + { + newargs = lappend(newargs, lfirst(arg)); + continue; + } + const_input = (Const *) lfirst(arg); + if (const_input->constisnull) + haveNull = true; + else if (DatumGetInt32(const_input->constvalue)) + forceTrue = true; + /* otherwise, we can drop the constant-false input */ + } + /* + * We could return TRUE before falling out of the loop, + * but this coding method will be easier to adapt if + * we ever add a notion of non-removable functions. + * We'd need to check all the inputs for non-removability. + */ + if (forceTrue) + return MAKEBOOLCONST(true, false); + if (haveNull) + newargs = lappend(newargs, MAKEBOOLCONST(false, true)); + /* If all the inputs are FALSE, result is FALSE */ + if (newargs == NIL) + return MAKEBOOLCONST(false, false); + /* If only one nonconst-or-NULL input, it's the result */ + if (lnext(newargs) == NIL) + return (Node *) lfirst(newargs); + /* Else we still need an OR node */ + return (Node *) make_orclause(newargs); + } + case AND_EXPR: + { + /* + * AND arguments are handled as follows: + * non constant: keep + * TRUE: drop (does not affect result) + * FALSE: force result to FALSE + * NULL: keep only one + * We keep one NULL input because ExecEvalAnd returns + * NULL when no input is FALSE and at least one is NULL. + */ + List *newargs = NIL; + List *arg; + bool haveNull = false; + bool forceFalse = false; + + foreach(arg, args) + { + if (! IsA(lfirst(arg), Const)) + { + newargs = lappend(newargs, lfirst(arg)); + continue; + } + const_input = (Const *) lfirst(arg); + if (const_input->constisnull) + haveNull = true; + else if (! DatumGetInt32(const_input->constvalue)) + forceFalse = true; + /* otherwise, we can drop the constant-true input */ + } + /* + * We could return FALSE before falling out of the loop, + * but this coding method will be easier to adapt if + * we ever add a notion of non-removable functions. + * We'd need to check all the inputs for non-removability. + */ + if (forceFalse) + return MAKEBOOLCONST(false, false); + if (haveNull) + newargs = lappend(newargs, MAKEBOOLCONST(false, true)); + /* If all the inputs are TRUE, result is TRUE */ + if (newargs == NIL) + return MAKEBOOLCONST(true, false); + /* If only one nonconst-or-NULL input, it's the result */ + if (lnext(newargs) == NIL) + return (Node *) lfirst(newargs); + /* Else we still need an AND node */ + return (Node *) make_andclause(newargs); + } + case NOT_EXPR: + Assert(length(args) == 1); + if (! IsA(lfirst(args), Const)) + break; + const_input = (Const *) lfirst(args); + /* NOT NULL => NULL */ + if (const_input->constisnull) + return MAKEBOOLCONST(false, true); + /* otherwise pretty easy */ + return MAKEBOOLCONST(! DatumGetInt32(const_input->constvalue), + false); + case SUBPLAN_EXPR: + /* + * Safety measure per notes at head of this routine: + * return a SubPlan unchanged. Too late to do anything + * with it. The arglist simplification above was wasted + * work (the list probably only contains Var nodes anyway). + */ + return (Node *) expr; + default: + elog(ERROR, "eval_const_expressions: unexpected opType %d", + (int) expr->opType); + break; + } + + /* + * If we break out of the above switch on opType, then the + * expression cannot be simplified any further, so build + * and return a replacement Expr node using the + * possibly-simplified arguments and the original oper node. + * Can't use make_clause() here because we want to be sure + * the typeOid field is preserved... + */ + newexpr = makeNode(Expr); + newexpr->typeOid = expr->typeOid; + newexpr->opType = expr->opType; + newexpr->oper = expr->oper; + newexpr->args = args; + return (Node *) newexpr; + } + if (IsA(node, CaseExpr)) + { + /* + * CASE expressions can be simplified if there are constant condition + * clauses: + * FALSE (or NULL): drop the alternative + * TRUE: drop all remaining alternatives + * If the first non-FALSE alternative is a constant TRUE, we can + * simplify the entire CASE to that alternative's expression. + * If there are no non-FALSE alternatives, we simplify the entire + * CASE to the default result (ELSE result). + */ + CaseExpr *caseexpr = (CaseExpr *) node; + CaseExpr *newcase; + List *newargs = NIL; + Node *defresult; + Const *const_input; + List *arg; + + foreach(arg, caseexpr->args) + { + /* Simplify this alternative's condition and result */ + CaseWhen *casewhen = (CaseWhen *) + expression_tree_mutator((Node *) lfirst(arg), + eval_const_expressions_mutator, + (void *) context); + Assert(IsA(casewhen, CaseWhen)); + if (casewhen->expr == NULL || + ! IsA(casewhen->expr, Const)) + { + newargs = lappend(newargs, casewhen); + continue; + } + const_input = (Const *) casewhen->expr; + if (const_input->constisnull || + ! DatumGetInt32(const_input->constvalue)) + continue; /* drop alternative with FALSE condition */ + /* + * Found a TRUE condition. If it's the first (un-dropped) + * alternative, the CASE reduces to just this alternative. + */ + if (newargs == NIL) + return casewhen->result; + /* + * Otherwise, add it to the list, and drop all the rest. + */ + newargs = lappend(newargs, casewhen); + break; + } + + /* Simplify the default result */ + defresult = eval_const_expressions_mutator(caseexpr->defresult, + context); + /* If no non-FALSE alternatives, CASE reduces to the default result */ + if (newargs == NIL) + return defresult; + /* Otherwise we need a new CASE node */ + newcase = makeNode(CaseExpr); + newcase->casetype = caseexpr->casetype; + newcase->arg = NULL; + newcase->args = newargs; + newcase->defresult = defresult; + return (Node *) newcase; + } + if (IsA(node, Iter)) + { + /* + * The argument of an Iter is normally a function call. + * We must not try to eliminate the function, but we + * can try to simplify its arguments. If, by chance, + * the arg is NOT a function then we go ahead and try to + * simplify it (by falling into expression_tree_mutator). + * Is that the right thing? + */ + Iter *iter = (Iter *) node; + + if (is_funcclause(iter->iterexpr)) + { + Expr *func = (Expr *) iter->iterexpr; + Expr *newfunc; + Iter *newiter; + + newfunc = makeNode(Expr); + newfunc->typeOid = func->typeOid; + newfunc->opType = func->opType; + newfunc->oper = func->oper; + newfunc->args = (List *) + expression_tree_mutator((Node *) func->args, + eval_const_expressions_mutator, + (void *) context); + newiter = makeNode(Iter); + newiter->iterexpr = (Node *) newfunc; + newiter->itertype = iter->itertype; + return (Node *) newiter; + } + } + /* + * For any node type not handled above, we recurse using + * expression_tree_mutator, which will copy the node unchanged + * but try to simplify its arguments (if any) using this routine. + * For example: we cannot eliminate an ArrayRef node, but we + * might be able to simplify constant expressions in its subscripts. + */ + return expression_tree_mutator(node, eval_const_expressions_mutator, + (void *) context); +} + /* * Standard expression-tree walking support * |
