summaryrefslogtreecommitdiff
path: root/src/backend/optimizer
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r--src/backend/optimizer/plan/planmain.c35
-rw-r--r--src/backend/optimizer/plan/planner.c39
-rw-r--r--src/backend/optimizer/util/clauses.c421
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
*