mirror of
https://github.com/postgres/postgres.git
synced 2025-11-07 19:06:32 +03:00
Fix planner and rewriter to follow SQL semantics for tables that are
mentioned in FROM but not elsewhere in the query: such tables should be joined over anyway. Aside from being more standards-compliant, this allows removal of some very ugly hacks for COUNT(*) processing. Also, allow HAVING clause without aggregate functions, since SQL does. Clean up CREATE RULE statement-list syntax the same way Bruce just fixed the main stmtmulti production. CAUTION: addition of a field to RangeTblEntry nodes breaks stored rules; you will have to initdb if you have any rules.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.39 1999/08/26 05:07:41 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.40 1999/10/07 04:23:06 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -77,15 +77,21 @@ add_vars_to_targetlist(Query *root, List *vars)
|
||||
}
|
||||
|
||||
/*
|
||||
* add_missing_vars_to_tlist
|
||||
* If we have range variable(s) in the FROM clause that does not appear
|
||||
* in the target list nor qualifications, we add it to the base relation
|
||||
* list. For instance, "select f.x from foo f, foo f2" is a join of f and
|
||||
* f2. Note that if we have "select foo.x from foo f", it also gets turned
|
||||
* into a join.
|
||||
* add_missing_rels_to_query
|
||||
*
|
||||
* If we have a range variable in the FROM clause that does not appear
|
||||
* in the target list nor qualifications, we must add it to the base
|
||||
* relation list so that it will be joined. For instance, "select f.x
|
||||
* from foo f, foo f2" is a join of f and f2. Note that if we have
|
||||
* "select foo.x from foo f", it also gets turned into a join (between
|
||||
* foo as foo and foo as f).
|
||||
*
|
||||
* To avoid putting useless entries into the per-relation targetlists,
|
||||
* this should only be called after all the variables in the targetlist
|
||||
* and quals have been processed by the routines above.
|
||||
*/
|
||||
void
|
||||
add_missing_vars_to_tlist(Query *root, List *tlist)
|
||||
add_missing_rels_to_query(Query *root)
|
||||
{
|
||||
int varno = 1;
|
||||
List *l;
|
||||
@@ -93,21 +99,21 @@ add_missing_vars_to_tlist(Query *root, List *tlist)
|
||||
foreach(l, root->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
|
||||
Relids relids;
|
||||
|
||||
relids = lconsi(varno, NIL);
|
||||
if (rte->inFromCl && !rel_member(relids, root->base_rel_list))
|
||||
if (rte->inJoinSet)
|
||||
{
|
||||
RelOptInfo *rel;
|
||||
Var *var;
|
||||
RelOptInfo *rel = get_base_rel(root, varno);
|
||||
|
||||
/* add it to base_rel_list */
|
||||
rel = get_base_rel(root, varno);
|
||||
/* give it a dummy tlist entry for its OID */
|
||||
var = makeVar(varno, ObjectIdAttributeNumber, OIDOID, -1, 0);
|
||||
add_var_to_tlist(rel, var);
|
||||
/* If the rel isn't otherwise referenced, give it a dummy
|
||||
* targetlist consisting of its own OID.
|
||||
*/
|
||||
if (rel->targetlist == NIL)
|
||||
{
|
||||
Var *var = makeVar(varno, ObjectIdAttributeNumber,
|
||||
OIDOID, -1, 0);
|
||||
add_var_to_tlist(rel, var);
|
||||
}
|
||||
}
|
||||
pfree(relids);
|
||||
varno++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.45 1999/09/26 02:28:27 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.46 1999/10/07 04:23:06 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
static Plan *subplanner(Query *root, List *flat_tlist, List *qual);
|
||||
|
||||
|
||||
/*
|
||||
* query_planner
|
||||
* Routine to create a query plan. It does so by first creating a
|
||||
@@ -39,9 +40,8 @@ static Plan *subplanner(Query *root, List *flat_tlist, List *qual);
|
||||
* be placed where and any relation level qualifications to be
|
||||
* satisfied.
|
||||
*
|
||||
* command-type is the query command, e.g., select, delete, etc.
|
||||
* tlist is the target list of the query
|
||||
* qual is the qualification of the query
|
||||
* tlist is the target list of the query (do NOT use root->targetList!)
|
||||
* qual is the qualification of the query (likewise!)
|
||||
*
|
||||
* Note: the Query node now also includes a query_pathkeys field, which
|
||||
* is both an input and an output of query_planner(). The input value
|
||||
@@ -57,25 +57,20 @@ static Plan *subplanner(Query *root, List *flat_tlist, List *qual);
|
||||
*/
|
||||
Plan *
|
||||
query_planner(Query *root,
|
||||
int command_type,
|
||||
List *tlist,
|
||||
List *qual)
|
||||
{
|
||||
List *constant_qual = NIL;
|
||||
List *var_only_tlist;
|
||||
List *level_tlist;
|
||||
Plan *subplan;
|
||||
|
||||
/*
|
||||
* Simplify constant expressions in both targetlist and qual.
|
||||
* Note: union_planner should already have done constant folding
|
||||
* in both the tlist and qual, so we don't do it again here
|
||||
* (indeed, we may be getting a flattened var-only tlist anyway).
|
||||
*
|
||||
* 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.
|
||||
* Is there any value in re-folding the qual after canonicalize_qual?
|
||||
*/
|
||||
tlist = (List *) eval_const_expressions((Node *) tlist);
|
||||
qual = (List *) eval_const_expressions((Node *) qual);
|
||||
|
||||
/*
|
||||
* Canonicalize the qual, and convert it to implicit-AND format.
|
||||
@@ -97,97 +92,75 @@ query_planner(Query *root,
|
||||
qual = (List *) SS_process_sublinks((Node *) qual);
|
||||
|
||||
/*
|
||||
* Pull out any non-variable qualifications so these can be put in the
|
||||
* topmost result node. (Any *really* non-variable quals will probably
|
||||
* If the query contains no relation references at all, it must be
|
||||
* something like "SELECT 2+2;". Build a trivial "Result" plan.
|
||||
*/
|
||||
if (root->rtable == NIL)
|
||||
{
|
||||
/* If it's not a select, it should have had a target relation... */
|
||||
if (root->commandType != CMD_SELECT)
|
||||
elog(ERROR, "Empty range table for non-SELECT query");
|
||||
|
||||
root->query_pathkeys = NIL; /* signal unordered result */
|
||||
|
||||
/* Make childless Result node to evaluate given tlist. */
|
||||
return (Plan *) make_result(tlist, (Node *) qual, (Plan *) NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Pull out any non-variable qual clauses so these can be put in a
|
||||
* toplevel "Result" node, where they will gate execution of the whole
|
||||
* plan (the Result will not invoke its descendant plan unless the
|
||||
* quals are true). Note that any *really* non-variable quals will
|
||||
* have been optimized away by eval_const_expressions(). What we're
|
||||
* looking for here is quals that depend only on outer-level vars...)
|
||||
* mostly interested in here is quals that depend only on outer-level
|
||||
* vars, although if the qual reduces to "WHERE FALSE" this path will
|
||||
* also be taken.
|
||||
*/
|
||||
qual = pull_constant_clauses(qual, &constant_qual);
|
||||
|
||||
/*
|
||||
* Create a target list that consists solely of (resdom var) target
|
||||
* list entries, i.e., contains no arbitrary expressions.
|
||||
*/
|
||||
var_only_tlist = flatten_tlist(tlist);
|
||||
if (var_only_tlist)
|
||||
level_tlist = var_only_tlist;
|
||||
else
|
||||
/* from old code. the logic is beyond me. - ay 2/95 */
|
||||
level_tlist = tlist;
|
||||
|
||||
/*
|
||||
* A query may have a non-variable target list and a non-variable
|
||||
* qualification only under certain conditions: - the query creates
|
||||
* all-new tuples, or - the query is a replace (a scan must still be
|
||||
* done in this case).
|
||||
*/
|
||||
if (var_only_tlist == NULL && qual == NULL)
|
||||
{
|
||||
root->query_pathkeys = NIL; /* these plans make unordered results */
|
||||
|
||||
switch (command_type)
|
||||
{
|
||||
case CMD_SELECT:
|
||||
case CMD_INSERT:
|
||||
return ((Plan *) make_result(tlist,
|
||||
(Node *) constant_qual,
|
||||
(Plan *) NULL));
|
||||
break;
|
||||
case CMD_DELETE:
|
||||
case CMD_UPDATE:
|
||||
{
|
||||
SeqScan *scan = make_seqscan(tlist,
|
||||
NIL,
|
||||
root->resultRelation);
|
||||
|
||||
if (constant_qual != NULL)
|
||||
return ((Plan *) make_result(tlist,
|
||||
(Node *) constant_qual,
|
||||
(Plan *) scan));
|
||||
else
|
||||
return (Plan *) scan;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return (Plan *) NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Choose the best access path and build a plan for it.
|
||||
*/
|
||||
subplan = subplanner(root, level_tlist, qual);
|
||||
|
||||
/*
|
||||
* Build a result node linking the plan if we have constant quals
|
||||
*/
|
||||
if (constant_qual)
|
||||
{
|
||||
subplan = (Plan *) make_result(tlist,
|
||||
(Node *) constant_qual,
|
||||
subplan);
|
||||
|
||||
root->query_pathkeys = NIL; /* result is unordered, no? */
|
||||
|
||||
return subplan;
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace the toplevel plan node's flattened target list with the
|
||||
* targetlist given by my caller, so that expressions are evaluated.
|
||||
*
|
||||
* All subplan nodes will have "flat" (var-only) tlists.
|
||||
*
|
||||
* This implies that all expression evaluations are done at the root
|
||||
* of the plan tree. Once upon a time there was code to try to push
|
||||
* expensive function calls down to lower plan nodes, but that's dead
|
||||
* code and has been for a long time...
|
||||
*/
|
||||
var_only_tlist = flatten_tlist(tlist);
|
||||
|
||||
/*
|
||||
* Choose the best access path and build a plan for it.
|
||||
*/
|
||||
subplan = subplanner(root, var_only_tlist, qual);
|
||||
|
||||
/*
|
||||
* Build a result node to control the plan if we have constant quals.
|
||||
*/
|
||||
if (constant_qual)
|
||||
{
|
||||
/*
|
||||
* The result node will also be responsible for evaluating
|
||||
* the originally requested tlist.
|
||||
*/
|
||||
subplan = (Plan *) make_result(tlist,
|
||||
(Node *) constant_qual,
|
||||
subplan);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Replace the toplevel plan node's flattened target list with the
|
||||
* targetlist given by my caller, so that expressions are evaluated.
|
||||
*/
|
||||
subplan->targetlist = tlist;
|
||||
|
||||
return subplan;
|
||||
}
|
||||
|
||||
return subplan;
|
||||
|
||||
#ifdef NOT_USED
|
||||
|
||||
/*
|
||||
@@ -230,12 +203,31 @@ subplanner(Query *root,
|
||||
|
||||
make_var_only_tlist(root, flat_tlist);
|
||||
add_restrict_and_join_to_rels(root, qual);
|
||||
add_missing_vars_to_tlist(root, flat_tlist);
|
||||
add_missing_rels_to_query(root);
|
||||
|
||||
set_joininfo_mergeable_hashable(root->base_rel_list);
|
||||
|
||||
final_rel = make_one_rel(root, root->base_rel_list);
|
||||
|
||||
if (! final_rel)
|
||||
{
|
||||
/*
|
||||
* We expect to end up here for a trivial INSERT ... VALUES query
|
||||
* (which will have a target relation, so it gets past query_planner's
|
||||
* check for empty range table; but the target rel is unreferenced
|
||||
* and not marked inJoinSet, so we find there is nothing to join).
|
||||
*
|
||||
* It's also possible to get here if the query was rewritten by the
|
||||
* rule processor (creating rangetable entries not marked inJoinSet)
|
||||
* but the rules either did nothing or were simplified to nothing
|
||||
* by constant-expression folding. So, don't complain.
|
||||
*/
|
||||
root->query_pathkeys = NIL; /* signal unordered result */
|
||||
|
||||
/* Make childless Result node to evaluate given tlist. */
|
||||
return (Plan *) make_result(flat_tlist, (Node *) qual, (Plan *) NULL);
|
||||
}
|
||||
|
||||
#ifdef NOT_USED /* fix xfunc */
|
||||
|
||||
/*
|
||||
@@ -259,13 +251,6 @@ subplanner(Query *root,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (! final_rel)
|
||||
{
|
||||
elog(NOTICE, "final relation is null");
|
||||
root->query_pathkeys = NIL; /* result is unordered, no? */
|
||||
return create_plan((Path *) NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine the cheapest path and create a subplan to execute it.
|
||||
*
|
||||
@@ -344,10 +329,11 @@ subplanner(Query *root,
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing for it but to sort the cheapestpath --- but we let the
|
||||
/*
|
||||
* Nothing for it but to sort the cheapestpath --- but we let the
|
||||
* caller do that. union_planner has to be able to add a sort node
|
||||
* anyway, so no need for extra code here. (Furthermore, the given
|
||||
* pathkeys might involve something we can't compute yet, such as
|
||||
* pathkeys might involve something we can't compute here, such as
|
||||
* an aggregate function...)
|
||||
*/
|
||||
root->query_pathkeys = final_rel->cheapestpath->pathkeys;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.69 1999/09/26 02:28:27 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.70 1999/10/07 04:23:06 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -94,6 +94,37 @@ union_planner(Query *parse)
|
||||
List *current_pathkeys = NIL;
|
||||
Index rt_index;
|
||||
|
||||
/*
|
||||
* A HAVING clause without aggregates is equivalent to a WHERE clause
|
||||
* (except it can only refer to grouped fields). If there are no
|
||||
* aggs anywhere in the query, then we don't want to create an Agg
|
||||
* plan node, so merge the HAVING condition into WHERE. (We used to
|
||||
* consider this an error condition, but it seems to be legal SQL.)
|
||||
*/
|
||||
if (parse->havingQual != NULL && ! parse->hasAggs)
|
||||
{
|
||||
if (parse->qual == NULL)
|
||||
parse->qual = parse->havingQual;
|
||||
else
|
||||
parse->qual = (Node *) make_andclause(lappend(lcons(parse->qual,
|
||||
NIL),
|
||||
parse->havingQual));
|
||||
parse->havingQual = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Simplify constant expressions in targetlist and quals.
|
||||
*
|
||||
* 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);
|
||||
parse->qual = eval_const_expressions(parse->qual);
|
||||
parse->havingQual = eval_const_expressions(parse->havingQual);
|
||||
|
||||
|
||||
if (parse->unionClause)
|
||||
{
|
||||
result_plan = (Plan *) plan_union_queries(parse);
|
||||
@@ -221,7 +252,6 @@ union_planner(Query *parse)
|
||||
|
||||
/* Generate the (sub) plan */
|
||||
result_plan = query_planner(parse,
|
||||
parse->commandType,
|
||||
sub_tlist,
|
||||
(List *) parse->qual);
|
||||
|
||||
@@ -301,25 +331,6 @@ union_planner(Query *parse)
|
||||
*/
|
||||
if (parse->havingQual)
|
||||
{
|
||||
/*--------------------
|
||||
* 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);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.53 1999/10/02 04:37:52 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.54 1999/10/07 04:23:08 tgl Exp $
|
||||
*
|
||||
* HISTORY
|
||||
* AUTHOR DATE MAJOR EVENT
|
||||
@@ -34,6 +34,11 @@
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
/* 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))
|
||||
|
||||
typedef struct {
|
||||
List *groupClause;
|
||||
List *targetList;
|
||||
@@ -312,17 +317,20 @@ make_andclause(List *andclauses)
|
||||
}
|
||||
|
||||
/*
|
||||
* Sometimes (such as in the result of cnfify), we use lists of expression
|
||||
* nodes with implicit AND semantics. These functions convert between an
|
||||
* AND-semantics expression list and the ordinary representation of a
|
||||
* boolean expression.
|
||||
* Sometimes (such as in the result of canonicalize_qual or the input of
|
||||
* ExecQual), we use lists of expression nodes with implicit AND semantics.
|
||||
*
|
||||
* These functions convert between an AND-semantics expression list and the
|
||||
* ordinary representation of a boolean expression.
|
||||
*
|
||||
* Note that an empty list is considered equivalent to TRUE.
|
||||
*/
|
||||
Expr *
|
||||
make_ands_explicit(List *andclauses)
|
||||
{
|
||||
if (andclauses == NIL)
|
||||
return NULL;
|
||||
else if (length(andclauses) == 1)
|
||||
return (Expr *) MAKEBOOLCONST(true, false);
|
||||
else if (lnext(andclauses) == NIL)
|
||||
return (Expr *) lfirst(andclauses);
|
||||
else
|
||||
return make_andclause(andclauses);
|
||||
@@ -331,10 +339,20 @@ make_ands_explicit(List *andclauses)
|
||||
List *
|
||||
make_ands_implicit(Expr *clause)
|
||||
{
|
||||
/*
|
||||
* NB: because the parser sets the qual field to NULL in a query that
|
||||
* has no WHERE clause, we must consider a NULL input clause as TRUE,
|
||||
* even though one might more reasonably think it FALSE. Grumble.
|
||||
* If this causes trouble, consider changing the parser's behavior.
|
||||
*/
|
||||
if (clause == NULL)
|
||||
return NIL;
|
||||
return NIL; /* NULL -> NIL list == TRUE */
|
||||
else if (and_clause((Node *) clause))
|
||||
return clause->args;
|
||||
else if (IsA(clause, Const) &&
|
||||
! ((Const *) clause)->constisnull &&
|
||||
DatumGetInt32(((Const *) clause)->constvalue))
|
||||
return NIL; /* constant TRUE input -> NIL list */
|
||||
else
|
||||
return lcons(clause, NIL);
|
||||
}
|
||||
@@ -808,11 +826,6 @@ eval_const_expressions(Node *node)
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user