Commit 3eb1c822 authored by Tom Lane's avatar Tom Lane

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.
parent 4040fcfa
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* *
* Copyright (c) 1994, Regents of the University of California * Copyright (c) 1994, Regents of the University of California
* *
* $Id: view.c,v 1.38 1999/10/03 23:55:27 tgl Exp $ * $Id: view.c,v 1.39 1999/10/07 04:23:00 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -225,9 +225,9 @@ UpdateRangeTableOfViewParse(char *viewName, Query *viewParse) ...@@ -225,9 +225,9 @@ UpdateRangeTableOfViewParse(char *viewName, Query *viewParse)
* table... CURRENT first, then NEW.... * table... CURRENT first, then NEW....
*/ */
rt_entry1 = addRangeTableEntry(NULL, (char *) viewName, "*CURRENT*", rt_entry1 = addRangeTableEntry(NULL, (char *) viewName, "*CURRENT*",
FALSE, FALSE); FALSE, FALSE, FALSE);
rt_entry2 = addRangeTableEntry(NULL, (char *) viewName, "*NEW*", rt_entry2 = addRangeTableEntry(NULL, (char *) viewName, "*NEW*",
FALSE, FALSE); FALSE, FALSE, FALSE);
new_rt = lcons(rt_entry2, old_rt); new_rt = lcons(rt_entry2, old_rt);
new_rt = lcons(rt_entry1, new_rt); new_rt = lcons(rt_entry1, new_rt);
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.96 1999/09/29 16:06:02 wieck Exp $ * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.97 1999/10/07 04:23:01 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1514,8 +1514,7 @@ ExecRelCheck(Relation rel, HeapTuple tuple, EState *estate) ...@@ -1514,8 +1514,7 @@ ExecRelCheck(Relation rel, HeapTuple tuple, EState *estate)
rte->relname = nameout(&(rel->rd_rel->relname)); rte->relname = nameout(&(rel->rd_rel->relname));
rte->refname = rte->relname; rte->refname = rte->relname;
rte->relid = RelationGetRelid(rel); rte->relid = RelationGetRelid(rel);
rte->inh = false; /* inh, inFromCl, inJoinSet, skipAcl won't be used, leave them zero */
rte->inFromCl = true;
rtlist = lcons(rte, NIL); rtlist = lcons(rte, NIL);
econtext->ecxt_scantuple = slot; /* scan tuple slot */ econtext->ecxt_scantuple = slot; /* scan tuple slot */
econtext->ecxt_innertuple = NULL; /* inner tuple slot */ econtext->ecxt_innertuple = NULL; /* inner tuple slot */
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.92 1999/08/21 03:48:57 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.93 1999/10/07 04:23:03 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1316,9 +1316,9 @@ _copyRangeTblEntry(RangeTblEntry *from) ...@@ -1316,9 +1316,9 @@ _copyRangeTblEntry(RangeTblEntry *from)
newnode->relid = from->relid; newnode->relid = from->relid;
newnode->inh = from->inh; newnode->inh = from->inh;
newnode->inFromCl = from->inFromCl; newnode->inFromCl = from->inFromCl;
newnode->inJoinSet = from->inJoinSet;
newnode->skipAcl = from->skipAcl; newnode->skipAcl = from->skipAcl;
return newnode; return newnode;
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.49 1999/09/26 02:28:21 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.50 1999/10/07 04:23:04 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -597,6 +597,8 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b) ...@@ -597,6 +597,8 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
return false; return false;
if (a->inFromCl != b->inFromCl) if (a->inFromCl != b->inFromCl)
return false; return false;
if (a->inJoinSet != b->inJoinSet)
return false;
if (a->skipAcl != b->skipAcl) if (a->skipAcl != b->skipAcl)
return false; return false;
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* *
* Copyright (c) 1994, Regents of the University of California * Copyright (c) 1994, Regents of the University of California
* *
* $Id: outfuncs.c,v 1.96 1999/10/03 23:55:29 tgl Exp $ * $Id: outfuncs.c,v 1.97 1999/10/07 04:23:04 tgl Exp $
* *
* NOTES * NOTES
* Every (plan) node in POSTGRES has an associated "out" routine which * Every (plan) node in POSTGRES has an associated "out" routine which
...@@ -864,12 +864,13 @@ static void ...@@ -864,12 +864,13 @@ static void
_outRangeTblEntry(StringInfo str, RangeTblEntry *node) _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
{ {
appendStringInfo(str, appendStringInfo(str,
" RTE :relname %s :refname %s :relid %u :inh %s :inFromCl %s :skipAcl %s", " RTE :relname %s :refname %s :relid %u :inh %s :inFromCl %s :inJoinSet %s :skipAcl %s",
stringStringInfo(node->relname), stringStringInfo(node->relname),
stringStringInfo(node->refname), stringStringInfo(node->refname),
node->relid, node->relid,
node->inh ? "true" : "false", node->inh ? "true" : "false",
node->inFromCl ? "true" : "false", node->inFromCl ? "true" : "false",
node->inJoinSet ? "true" : "false",
node->skipAcl ? "true" : "false"); node->skipAcl ? "true" : "false");
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.73 1999/08/21 03:48:58 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.74 1999/10/07 04:23:04 tgl Exp $
* *
* NOTES * NOTES
* Most of the read functions for plan nodes are tested. (In fact, they * Most of the read functions for plan nodes are tested. (In fact, they
...@@ -1380,6 +1380,10 @@ _readRangeTblEntry() ...@@ -1380,6 +1380,10 @@ _readRangeTblEntry()
token = lsptok(NULL, &length); /* get :inFromCl */ token = lsptok(NULL, &length); /* get :inFromCl */
local_node->inFromCl = (token[0] == 't') ? true : false; local_node->inFromCl = (token[0] == 't') ? true : false;
token = lsptok(NULL, &length); /* eat :inJoinSet */
token = lsptok(NULL, &length); /* get :inJoinSet */
local_node->inJoinSet = (token[0] == 't') ? true : false;
token = lsptok(NULL, &length); /* eat :skipAcl */ token = lsptok(NULL, &length); /* eat :skipAcl */
token = lsptok(NULL, &length); /* get :skipAcl */ token = lsptok(NULL, &length); /* get :skipAcl */
local_node->skipAcl = (token[0] == 't') ? true : false; local_node->skipAcl = (token[0] == 't') ? true : false;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -77,15 +77,21 @@ add_vars_to_targetlist(Query *root, List *vars)
} }
/* /*
* add_missing_vars_to_tlist * add_missing_rels_to_query
* 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 * If we have a range variable in the FROM clause that does not appear
* list. For instance, "select f.x from foo f, foo f2" is a join of f and * in the target list nor qualifications, we must add it to the base
* f2. Note that if we have "select foo.x from foo f", it also gets turned * relation list so that it will be joined. For instance, "select f.x
* into a join. * 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 void
add_missing_vars_to_tlist(Query *root, List *tlist) add_missing_rels_to_query(Query *root)
{ {
int varno = 1; int varno = 1;
List *l; List *l;
...@@ -93,21 +99,21 @@ add_missing_vars_to_tlist(Query *root, List *tlist) ...@@ -93,21 +99,21 @@ add_missing_vars_to_tlist(Query *root, List *tlist)
foreach(l, root->rtable) foreach(l, root->rtable)
{ {
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
Relids relids;
relids = lconsi(varno, NIL); if (rte->inJoinSet)
if (rte->inFromCl && !rel_member(relids, root->base_rel_list))
{ {
RelOptInfo *rel; RelOptInfo *rel = get_base_rel(root, varno);
Var *var;
/* If the rel isn't otherwise referenced, give it a dummy
/* add it to base_rel_list */ * targetlist consisting of its own OID.
rel = get_base_rel(root, varno); */
/* give it a dummy tlist entry for its OID */ if (rel->targetlist == NIL)
var = makeVar(varno, ObjectIdAttributeNumber, OIDOID, -1, 0); {
add_var_to_tlist(rel, var); Var *var = makeVar(varno, ObjectIdAttributeNumber,
OIDOID, -1, 0);
add_var_to_tlist(rel, var);
}
} }
pfree(relids);
varno++; varno++;
} }
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -28,6 +28,7 @@
static Plan *subplanner(Query *root, List *flat_tlist, List *qual); static Plan *subplanner(Query *root, List *flat_tlist, List *qual);
/* /*
* query_planner * query_planner
* Routine to create a query plan. It does so by first creating a * 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); ...@@ -39,9 +40,8 @@ static Plan *subplanner(Query *root, List *flat_tlist, List *qual);
* be placed where and any relation level qualifications to be * be placed where and any relation level qualifications to be
* satisfied. * satisfied.
* *
* command-type is the query command, e.g., select, delete, etc. * tlist is the target list of the query (do NOT use root->targetList!)
* tlist is the target list of the query * qual is the qualification of the query (likewise!)
* qual is the qualification of the query
* *
* Note: the Query node now also includes a query_pathkeys field, which * 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 * 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); ...@@ -57,25 +57,20 @@ static Plan *subplanner(Query *root, List *flat_tlist, List *qual);
*/ */
Plan * Plan *
query_planner(Query *root, query_planner(Query *root,
int command_type,
List *tlist, List *tlist,
List *qual) List *qual)
{ {
List *constant_qual = NIL; List *constant_qual = NIL;
List *var_only_tlist; List *var_only_tlist;
List *level_tlist;
Plan *subplan; 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 * Is there any value in re-folding the qual after canonicalize_qual?
* 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. * Canonicalize the qual, and convert it to implicit-AND format.
...@@ -97,97 +92,75 @@ query_planner(Query *root, ...@@ -97,97 +92,75 @@ query_planner(Query *root,
qual = (List *) SS_process_sublinks((Node *) qual); qual = (List *) SS_process_sublinks((Node *) qual);
/* /*
* Pull out any non-variable qualifications so these can be put in the * If the query contains no relation references at all, it must be
* topmost result node. (Any *really* non-variable quals will probably * 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 * 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); qual = pull_constant_clauses(qual, &constant_qual);
/* /*
* Create a target list that consists solely of (resdom var) target * Create a target list that consists solely of (resdom var) target
* list entries, i.e., contains no arbitrary expressions. * list entries, i.e., contains no arbitrary expressions.
*
* 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); 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. * Choose the best access path and build a plan for it.
*/ */
subplan = subplanner(root, level_tlist, qual); subplan = subplanner(root, var_only_tlist, qual);
/* /*
* Build a result node linking the plan if we have constant quals * Build a result node to control the plan if we have constant quals.
*/ */
if (constant_qual) if (constant_qual)
{ {
/*
* The result node will also be responsible for evaluating
* the originally requested tlist.
*/
subplan = (Plan *) make_result(tlist, subplan = (Plan *) make_result(tlist,
(Node *) constant_qual, (Node *) constant_qual,
subplan); 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.
*
* 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...
*/
else 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; subplan->targetlist = tlist;
return subplan;
} }
return subplan;
#ifdef NOT_USED #ifdef NOT_USED
/* /*
...@@ -230,12 +203,31 @@ subplanner(Query *root, ...@@ -230,12 +203,31 @@ subplanner(Query *root,
make_var_only_tlist(root, flat_tlist); make_var_only_tlist(root, flat_tlist);
add_restrict_and_join_to_rels(root, qual); 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); set_joininfo_mergeable_hashable(root->base_rel_list);
final_rel = make_one_rel(root, 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 */ #ifdef NOT_USED /* fix xfunc */
/* /*
...@@ -259,13 +251,6 @@ subplanner(Query *root, ...@@ -259,13 +251,6 @@ subplanner(Query *root,
} }
#endif #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. * Determine the cheapest path and create a subplan to execute it.
* *
...@@ -344,10 +329,11 @@ subplanner(Query *root, ...@@ -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 * 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 * 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...) * an aggregate function...)
*/ */
root->query_pathkeys = final_rel->cheapestpath->pathkeys; root->query_pathkeys = final_rel->cheapestpath->pathkeys;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -94,6 +94,37 @@ union_planner(Query *parse)
List *current_pathkeys = NIL; List *current_pathkeys = NIL;
Index rt_index; 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) if (parse->unionClause)
{ {
result_plan = (Plan *) plan_union_queries(parse); result_plan = (Plan *) plan_union_queries(parse);
...@@ -221,7 +252,6 @@ union_planner(Query *parse) ...@@ -221,7 +252,6 @@ union_planner(Query *parse)
/* Generate the (sub) plan */ /* Generate the (sub) plan */
result_plan = query_planner(parse, result_plan = query_planner(parse,
parse->commandType,
sub_tlist, sub_tlist,
(List *) parse->qual); (List *) parse->qual);
...@@ -301,25 +331,6 @@ union_planner(Query *parse) ...@@ -301,25 +331,6 @@ union_planner(Query *parse)
*/ */
if (parse->havingQual) 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 */ /* Convert the havingQual to implicit-AND normal form */
parse->havingQual = (Node *) parse->havingQual = (Node *)
canonicalize_qual((Expr *) parse->havingQual, true); canonicalize_qual((Expr *) parse->havingQual, true);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * 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 * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -34,6 +34,11 @@ ...@@ -34,6 +34,11 @@
#include "utils/syscache.h" #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 { typedef struct {
List *groupClause; List *groupClause;
List *targetList; List *targetList;
...@@ -312,17 +317,20 @@ make_andclause(List *andclauses) ...@@ -312,17 +317,20 @@ make_andclause(List *andclauses)
} }
/* /*
* Sometimes (such as in the result of cnfify), we use lists of expression * Sometimes (such as in the result of canonicalize_qual or the input of
* nodes with implicit AND semantics. These functions convert between an * ExecQual), we use lists of expression nodes with implicit AND semantics.
* AND-semantics expression list and the ordinary representation of a *
* boolean expression. * 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 * Expr *
make_ands_explicit(List *andclauses) make_ands_explicit(List *andclauses)
{ {
if (andclauses == NIL) if (andclauses == NIL)
return NULL; return (Expr *) MAKEBOOLCONST(true, false);
else if (length(andclauses) == 1) else if (lnext(andclauses) == NIL)
return (Expr *) lfirst(andclauses); return (Expr *) lfirst(andclauses);
else else
return make_andclause(andclauses); return make_andclause(andclauses);
...@@ -331,10 +339,20 @@ make_ands_explicit(List *andclauses) ...@@ -331,10 +339,20 @@ make_ands_explicit(List *andclauses)
List * List *
make_ands_implicit(Expr *clause) 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) if (clause == NULL)
return NIL; return NIL; /* NULL -> NIL list == TRUE */
else if (and_clause((Node *) clause)) else if (and_clause((Node *) clause))
return clause->args; return clause->args;
else if (IsA(clause, Const) &&
! ((Const *) clause)->constisnull &&
DatumGetInt32(((Const *) clause)->constvalue))
return NIL; /* constant TRUE input -> NIL list */
else else
return lcons(clause, NIL); return lcons(clause, NIL);
} }
...@@ -808,11 +826,6 @@ eval_const_expressions(Node *node) ...@@ -808,11 +826,6 @@ eval_const_expressions(Node *node)
return eval_const_expressions_mutator(node, NULL); 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 * static Node *
eval_const_expressions_mutator (Node *node, void *context) eval_const_expressions_mutator (Node *node, void *context)
{ {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* *
* Copyright (c) 1994, Regents of the University of California * Copyright (c) 1994, Regents of the University of California
* *
* $Id: analyze.c,v 1.120 1999/10/03 23:55:30 tgl Exp $ * $Id: analyze.c,v 1.121 1999/10/07 04:23:11 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -298,18 +298,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) ...@@ -298,18 +298,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs; qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause) if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry); parseCheckAggregates(pstate, qry);
/*
* If there is a havingQual but there are no aggregates, then there is
* something wrong with the query because HAVING must contain
* aggregates in its expressions! Otherwise the query could have been
* formulated using the WHERE clause.
*/
if (qry->havingQual && ! qry->hasAggs)
elog(ERROR, "SELECT/HAVING requires aggregates to be valid");
/* /*
* The INSERT INTO ... SELECT ... could have a UNION in child, so * The INSERT INTO ... SELECT ... could have a UNION in child, so
* unionClause may be false * unionClause may be false
...@@ -961,9 +952,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt) ...@@ -961,9 +952,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
nothing_qry->commandType = CMD_NOTHING; nothing_qry->commandType = CMD_NOTHING;
addRangeTableEntry(pstate, stmt->object->relname, "*CURRENT*", addRangeTableEntry(pstate, stmt->object->relname, "*CURRENT*",
FALSE, FALSE); FALSE, FALSE, FALSE);
addRangeTableEntry(pstate, stmt->object->relname, "*NEW*", addRangeTableEntry(pstate, stmt->object->relname, "*NEW*",
FALSE, FALSE); FALSE, FALSE, FALSE);
nothing_qry->rtable = pstate->p_rtable; nothing_qry->rtable = pstate->p_rtable;
...@@ -983,9 +974,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt) ...@@ -983,9 +974,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
* equal to 2. * equal to 2.
*/ */
addRangeTableEntry(pstate, stmt->object->relname, "*CURRENT*", addRangeTableEntry(pstate, stmt->object->relname, "*CURRENT*",
FALSE, FALSE); FALSE, FALSE, FALSE);
addRangeTableEntry(pstate, stmt->object->relname, "*NEW*", addRangeTableEntry(pstate, stmt->object->relname, "*NEW*",
FALSE, FALSE); FALSE, FALSE, FALSE);
pstate->p_last_resno = 1; pstate->p_last_resno = 1;
pstate->p_is_rule = true; /* for expand all */ pstate->p_is_rule = true; /* for expand all */
...@@ -1048,18 +1039,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) ...@@ -1048,18 +1039,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs; qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause) if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry); parseCheckAggregates(pstate, qry);
/*
* If there is a havingQual but there are no aggregates, then there is
* something wrong with the query because HAVING must contain
* aggregates in its expressions! Otherwise the query could have been
* formulated using the WHERE clause.
*/
if (qry->havingQual && ! qry->hasAggs)
elog(ERROR, "SELECT/HAVING requires aggregates to be valid");
/* /*
* The INSERT INTO ... SELECT ... could have a UNION in child, so * The INSERT INTO ... SELECT ... could have a UNION in child, so
* unionClause may be false * unionClause may be false
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.107 1999/10/05 18:14:31 momjian Exp $ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.108 1999/10/07 04:23:12 tgl Exp $
* *
* HISTORY * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -72,7 +72,6 @@ static char *xlateSqlType(char *); ...@@ -72,7 +72,6 @@ static char *xlateSqlType(char *);
static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr); static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
static Node *makeRowExpr(char *opr, List *largs, List *rargs); static Node *makeRowExpr(char *opr, List *largs, List *rargs);
static void mapTargetColumns(List *source, List *target); static void mapTargetColumns(List *source, List *target);
static char *fmtId(char *rawid);
static void param_type_init(Oid *typev, int nargs); static void param_type_init(Oid *typev, int nargs);
static Node *doNegate(Node *n); static Node *doNegate(Node *n);
...@@ -130,7 +129,7 @@ Oid param_type(int t); /* used in parse_expr.c */ ...@@ -130,7 +129,7 @@ Oid param_type(int t); /* used in parse_expr.c */
UpdateStmt, InsertStmt, select_clause, SelectStmt, NotifyStmt, DeleteStmt, UpdateStmt, InsertStmt, select_clause, SelectStmt, NotifyStmt, DeleteStmt,
ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt, ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt,
CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt, CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt,
ConstraintsSetStmt, RuleActionStmtOrEmpty, ConstraintsSetStmt,
%type <str> opt_database1, opt_database2, location, encoding %type <str> opt_database1, opt_database2, location, encoding
...@@ -165,7 +164,7 @@ Oid param_type(int t); /* used in parse_expr.c */ ...@@ -165,7 +164,7 @@ Oid param_type(int t); /* used in parse_expr.c */
result, relation_name_list, OptTableElementList, result, relation_name_list, OptTableElementList,
OptInherit, definition, OptInherit, definition,
opt_with, func_args, func_args_list, func_as, opt_with, func_args, func_args_list, func_as,
oper_argtypes, RuleActionList, RuleActionBlock, RuleActionMulti, oper_argtypes, RuleActionList, RuleActionMulti,
opt_column_list, columnList, opt_va_list, va_list, opt_column_list, columnList, opt_va_list, va_list,
sort_clause, sortby_list, index_params, index_list, name_list, sort_clause, sortby_list, index_params, index_list, name_list,
from_clause, from_expr, table_list, opt_array_bounds, from_clause, from_expr, table_list, opt_array_bounds,
...@@ -374,17 +373,18 @@ stmtblock: stmtmulti ...@@ -374,17 +373,18 @@ stmtblock: stmtmulti
{ parsetree = $1; } { parsetree = $1; }
; ;
/* the thrashing around here is to discard "empty" statements... */
stmtmulti: stmtmulti ';' stmt stmtmulti: stmtmulti ';' stmt
{ if ($3 != (Node *)NIL) { if ($3 != (Node *)NULL)
$$ = lappend($1, $3); $$ = lappend($1, $3);
else else
$$ = $1; $$ = $1;
} }
| stmt | stmt
{ if ($1 != (Node *)NIL) { if ($1 != (Node *)NULL)
$$ = lcons($1,NIL); $$ = lcons($1,NIL);
else else
$$ = (Node *)NIL; $$ = NIL;
} }
; ;
...@@ -433,7 +433,7 @@ stmt : AddAttrStmt ...@@ -433,7 +433,7 @@ stmt : AddAttrStmt
| VariableResetStmt | VariableResetStmt
| ConstraintsSetStmt | ConstraintsSetStmt
| /*EMPTY*/ | /*EMPTY*/
{ $$ = (Node *)NIL; } { $$ = (Node *)NULL; }
; ;
/***************************************************************************** /*****************************************************************************
...@@ -930,7 +930,7 @@ ColConstraint: ...@@ -930,7 +930,7 @@ ColConstraint:
CONSTRAINT name ColConstraintElem CONSTRAINT name ColConstraintElem
{ {
Constraint *n = (Constraint *)$3; Constraint *n = (Constraint *)$3;
if (n != NULL) n->name = fmtId($2); if (n != NULL) n->name = $2;
$$ = $3; $$ = $3;
} }
| ColConstraintElem | ColConstraintElem
...@@ -1024,7 +1024,7 @@ ColConstraintElem: CHECK '(' a_expr ')' ...@@ -1024,7 +1024,7 @@ ColConstraintElem: CHECK '(' a_expr ')'
TableConstraint: CONSTRAINT name ConstraintElem TableConstraint: CONSTRAINT name ConstraintElem
{ {
Constraint *n = (Constraint *)$3; Constraint *n = (Constraint *)$3;
if (n != NULL) n->name = fmtId($2); if (n != NULL) n->name = $2;
$$ = $3; $$ = $3;
} }
| ConstraintElem | ConstraintElem
...@@ -2034,20 +2034,23 @@ RuleStmt: CREATE RULE name AS ...@@ -2034,20 +2034,23 @@ RuleStmt: CREATE RULE name AS
RuleActionList: NOTHING { $$ = NIL; } RuleActionList: NOTHING { $$ = NIL; }
| SelectStmt { $$ = lcons($1, NIL); } | SelectStmt { $$ = lcons($1, NIL); }
| RuleActionStmt { $$ = lcons($1, NIL); } | RuleActionStmt { $$ = lcons($1, NIL); }
| '[' RuleActionBlock ']' { $$ = $2; } | '[' RuleActionMulti ']' { $$ = $2; }
| '(' RuleActionBlock ')' { $$ = $2; } | '(' RuleActionMulti ')' { $$ = $2; }
;
RuleActionBlock: RuleActionMulti { $$ = $1; }
| RuleActionStmt { $$ = lcons($1, NIL); }
; ;
RuleActionMulti: RuleActionMulti RuleActionStmt /* the thrashing around here is to discard "empty" statements... */
{ $$ = lappend($1, $2); } RuleActionMulti: RuleActionMulti ';' RuleActionStmtOrEmpty
| RuleActionMulti RuleActionStmt ';' { if ($3 != (Node *)NULL)
{ $$ = lappend($1, $2); } $$ = lappend($1, $3);
| RuleActionStmt ';' else
{ $$ = lcons($1, NIL); } $$ = $1;
}
| RuleActionStmtOrEmpty
{ if ($1 != (Node *)NULL)
$$ = lcons($1,NIL);
else
$$ = NIL;
}
; ;
RuleActionStmt: InsertStmt RuleActionStmt: InsertStmt
...@@ -2056,6 +2059,11 @@ RuleActionStmt: InsertStmt ...@@ -2056,6 +2059,11 @@ RuleActionStmt: InsertStmt
| NotifyStmt | NotifyStmt
; ;
RuleActionStmtOrEmpty: RuleActionStmt
| /*EMPTY*/
{ $$ = (Node *)NULL; }
;
event_object: relation_name '.' attr_name event_object: relation_name '.' attr_name
{ {
$$ = makeNode(Attr); $$ = makeNode(Attr);
...@@ -3676,12 +3684,25 @@ a_expr: attr ...@@ -3676,12 +3684,25 @@ a_expr: attr
{ $$ = makeA_Expr(OP, $2, $1, NULL); } { $$ = makeA_Expr(OP, $2, $1, NULL); }
| func_name '(' '*' ')' | func_name '(' '*' ')'
{ {
/* cheap hack for aggregate (eg. count) */ /*
* For now, we transform AGGREGATE(*) into AGGREGATE(1).
*
* This does the right thing for COUNT(*) (in fact,
* any certainly-non-null expression would do for COUNT),
* and there are no other aggregates in SQL92 that accept
* '*' as parameter.
*
* XXX really, the '*' ought to be transformed to some
* special construct that wouldn't be acceptable as the
* input of a non-aggregate function, in case the given
* func_name matches a plain function. This would also
* support a possible extension to let user-defined
* aggregates do something special with '*' as input.
*/
FuncCall *n = makeNode(FuncCall); FuncCall *n = makeNode(FuncCall);
A_Const *star = makeNode(A_Const); A_Const *star = makeNode(A_Const);
star->val.type = T_Integer;
star->val.type = T_String; star->val.val.ival = 1;
star->val.val.str = "";
n->funcname = $1; n->funcname = $1;
n->args = lcons(star, NIL); n->args = lcons(star, NIL);
$$ = (Node *)n; $$ = (Node *)n;
...@@ -5265,30 +5286,6 @@ void parser_init(Oid *typev, int nargs) ...@@ -5265,30 +5286,6 @@ void parser_init(Oid *typev, int nargs)
} }
/* fmtId()
* Check input string for non-lowercase/non-numeric characters.
* Returns either input string or input surrounded by double quotes.
*/
static char *
fmtId(char *rawid)
{
static char *cp;
for (cp = rawid; *cp != '\0'; cp++)
if (! (islower(*cp) || isdigit(*cp) || (*cp == '_'))) break;
if (*cp != '\0') {
cp = palloc(strlen(rawid)+3);
strcpy(cp,"\"");
strcat(cp,rawid);
strcat(cp,"\"");
} else {
cp = rawid;
};
return cp;
}
/* /*
* param_type_init() * param_type_init()
* *
...@@ -5313,6 +5310,11 @@ Oid param_type(int t) ...@@ -5313,6 +5310,11 @@ Oid param_type(int t)
* The optimizer doesn't like '-' 4 for index use. It only checks for * The optimizer doesn't like '-' 4 for index use. It only checks for
* Var '=' Const. It wants an integer of -4, so we try to merge the * Var '=' Const. It wants an integer of -4, so we try to merge the
* minus into the constant. * minus into the constant.
*
* This code is no longer essential as of 10/1999, since the optimizer
* now has a constant-subexpression simplifier. However, we can save
* a few cycles throughout the parse and rewrite stages if we collapse
* the minus into the constant sooner rather than later...
*/ */
static Node *doNegate(Node *n) static Node *doNegate(Node *n)
{ {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.28 1999/08/21 03:48:55 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.29 1999/10/07 04:23:12 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -129,8 +129,8 @@ parseCheckAggregates(ParseState *pstate, Query *qry) ...@@ -129,8 +129,8 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
List *groupClauses = NIL; List *groupClauses = NIL;
List *tl; List *tl;
/* This should only be called if we found aggregates or grouping */ /* This should only be called if we found aggregates, GROUP, or HAVING */
Assert(pstate->p_hasAggs || qry->groupClause); Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual);
/* /*
* Aggregates must never appear in WHERE clauses. (Note this check * Aggregates must never appear in WHERE clauses. (Note this check
...@@ -160,6 +160,15 @@ parseCheckAggregates(ParseState *pstate, Query *qry) ...@@ -160,6 +160,15 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
groupClauses = lcons(expr, groupClauses); groupClauses = lcons(expr, groupClauses);
} }
/*
* The expression specified in the HAVING clause can only contain
* aggregates, group columns and functions thereof. As with WHERE,
* we want to point the finger at HAVING before the target list.
*/
if (!exprIsAggOrGroupCol(qry->havingQual, groupClauses))
elog(ERROR,
"Illegal use of aggregates or non-group column in HAVING clause");
/* /*
* The target list can only contain aggregates, group columns and * The target list can only contain aggregates, group columns and
* functions thereof. * functions thereof.
...@@ -173,14 +182,6 @@ parseCheckAggregates(ParseState *pstate, Query *qry) ...@@ -173,14 +182,6 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
"Illegal use of aggregates or non-group column in target list"); "Illegal use of aggregates or non-group column in target list");
} }
/*
* The expression specified in the HAVING clause has the same
* restriction as those in the target list.
*/
if (!exprIsAggOrGroupCol(qry->havingQual, groupClauses))
elog(ERROR,
"Illegal use of aggregates or non-group column in HAVING clause");
/* Release the list storage (but not the pointed-to expressions!) */ /* Release the list storage (but not the pointed-to expressions!) */
freeList(groupClauses); freeList(groupClauses);
} }
...@@ -190,12 +191,12 @@ Aggref * ...@@ -190,12 +191,12 @@ Aggref *
ParseAgg(ParseState *pstate, char *aggname, Oid basetype, ParseAgg(ParseState *pstate, char *aggname, Oid basetype,
List *target, int precedence) List *target, int precedence)
{ {
HeapTuple theAggTuple;
Form_pg_aggregate aggform;
Oid fintype; Oid fintype;
Oid vartype;
Oid xfn1; Oid xfn1;
Form_pg_aggregate aggform; Oid vartype;
Aggref *aggref; Aggref *aggref;
HeapTuple theAggTuple;
bool usenulls = false; bool usenulls = false;
theAggTuple = SearchSysCacheTuple(AGGNAME, theAggTuple = SearchSysCacheTuple(AGGNAME,
...@@ -206,66 +207,19 @@ ParseAgg(ParseState *pstate, char *aggname, Oid basetype, ...@@ -206,66 +207,19 @@ ParseAgg(ParseState *pstate, char *aggname, Oid basetype,
elog(ERROR, "Aggregate %s does not exist", aggname); elog(ERROR, "Aggregate %s does not exist", aggname);
/* /*
* We do a major hack for count(*) here. * There used to be a really ugly hack for count(*) here.
*
* Count(*) poses several problems. First, we need a field that is
* guaranteed to be in the range table, and unique. Using a constant
* causes the optimizer to properly remove the aggragate from any
* elements of the query. Using just 'oid', which can not be null, in
* the parser fails on:
* *
* select count(*) from tab1, tab2 -- oid is not unique select * It's gone. Now, the grammar transforms count(*) into count(1),
* count(*) from viewtable -- views don't have real oids * which does the right thing. (It didn't use to do the right thing,
* because the optimizer had the wrong ideas about semantics of queries
* without explicit variables. Fixed as of Oct 1999 --- tgl.)
* *
* So, for an aggregate with parameter '*', we use the first valid range * Since "1" never evaluates as null, we currently have no need of
* table entry, and pick the first column from the table. We set a * the "usenulls" flag, but it should be kept around; in fact, we should
* flag to count nulls, because we could have nulls in that column. * extend the pg_aggregate table to let usenulls be specified as an
* * attribute of user-defined aggregates.
* It's an ugly job, but someone has to do it. bjm 1998/1/18
*/ */
if (nodeTag(lfirst(target)) == T_Const)
{
Const *con = (Const *) lfirst(target);
if (con->consttype == UNKNOWNOID && VARSIZE(con->constvalue) == VARHDRSZ)
{
Attr *attr = makeNode(Attr);
List *rtable,
*rlist;
RangeTblEntry *first_valid_rte;
Assert(lnext(target) == NULL);
if (pstate->p_is_rule)
rtable = lnext(lnext(pstate->p_rtable));
else
rtable = pstate->p_rtable;
first_valid_rte = NULL;
foreach(rlist, rtable)
{
RangeTblEntry *rte = lfirst(rlist);
/* only entries on outer(non-function?) scope */
if (!rte->inFromCl && rte != pstate->p_target_rangetblentry)
continue;
first_valid_rte = rte;
break;
}
if (first_valid_rte == NULL)
elog(ERROR, "Can't find column to do aggregate(*) on.");
attr->relname = first_valid_rte->refname;
attr->attrs = lcons(makeString(
get_attname(first_valid_rte->relid, 1)), NIL);
lfirst(target) = transformExpr(pstate, (Node *) attr, precedence);
usenulls = true;
}
}
aggform = (Form_pg_aggregate) GETSTRUCT(theAggTuple); aggform = (Form_pg_aggregate) GETSTRUCT(theAggTuple);
fintype = aggform->aggfinaltype; fintype = aggform->aggfinaltype;
xfn1 = aggform->aggtransfn1; xfn1 = aggform->aggtransfn1;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.45 1999/09/18 19:07:12 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.46 1999/10/07 04:23:12 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -60,6 +60,15 @@ makeRangeTable(ParseState *pstate, List *frmList, Node **qual) ...@@ -60,6 +60,15 @@ makeRangeTable(ParseState *pstate, List *frmList, Node **qual)
* setTargetTable * setTargetTable
* Add the target relation of INSERT or UPDATE to the range table, * Add the target relation of INSERT or UPDATE to the range table,
* and make the special links to it in the ParseState. * and make the special links to it in the ParseState.
*
* Note that the target is not marked as either inFromCl or inJoinSet.
* For INSERT, we don't want the target to be joined to; it's a
* destination of tuples, not a source. For UPDATE/DELETE, we do
* need to scan or join the target. This will happen without the
* inJoinSet flag because the planner's preprocess_targetlist()
* adds the destination's CTID attribute to the targetlist, and
* therefore the destination will be a referenced table even if
* there is no other use of any of its attributes. Tricky, eh?
*/ */
void void
setTargetTable(ParseState *pstate, char *relname) setTargetTable(ParseState *pstate, char *relname)
...@@ -69,7 +78,8 @@ setTargetTable(ParseState *pstate, char *relname) ...@@ -69,7 +78,8 @@ setTargetTable(ParseState *pstate, char *relname)
if ((refnameRangeTablePosn(pstate, relname, &sublevels_up) == 0) if ((refnameRangeTablePosn(pstate, relname, &sublevels_up) == 0)
|| (sublevels_up != 0)) || (sublevels_up != 0))
rte = addRangeTableEntry(pstate, relname, relname, FALSE, FALSE); rte = addRangeTableEntry(pstate, relname, relname,
FALSE, FALSE, FALSE);
else else
rte = refnameRangeTableEntry(pstate, relname); rte = refnameRangeTableEntry(pstate, relname);
...@@ -230,7 +240,8 @@ transformTableEntry(ParseState *pstate, RangeVar *r) ...@@ -230,7 +240,8 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
* we expand * to foo.x. * we expand * to foo.x.
*/ */
rte = addRangeTableEntry(pstate, relname, refname, baserel->inh, TRUE); rte = addRangeTableEntry(pstate, relname, refname,
baserel->inh, TRUE, TRUE);
return refname; return refname;
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.59 1999/09/29 18:16:04 momjian Exp $ * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.60 1999/10/07 04:23:12 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -274,7 +274,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, ...@@ -274,7 +274,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
rte = refnameRangeTableEntry(pstate, refname); rte = refnameRangeTableEntry(pstate, refname);
if (rte == NULL) if (rte == NULL)
{ {
rte = addRangeTableEntry(pstate, refname, refname,FALSE, FALSE); rte = addRangeTableEntry(pstate, refname, refname,
FALSE, FALSE, TRUE);
#ifdef WARN_FROM #ifdef WARN_FROM
elog(NOTICE,"Adding missing FROM-clause entry%s for table %s", elog(NOTICE,"Adding missing FROM-clause entry%s for table %s",
pstate->parentParseState != NULL ? " in subquery" : "", pstate->parentParseState != NULL ? " in subquery" : "",
...@@ -437,7 +438,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, ...@@ -437,7 +438,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
rte = refnameRangeTableEntry(pstate, refname); rte = refnameRangeTableEntry(pstate, refname);
if (rte == NULL) if (rte == NULL)
{ {
rte = addRangeTableEntry(pstate, refname, refname,FALSE, FALSE); rte = addRangeTableEntry(pstate, refname, refname,
FALSE, FALSE, TRUE);
#ifdef WARN_FROM #ifdef WARN_FROM
elog(NOTICE,"Adding missing FROM-clause entry%s for table %s", elog(NOTICE,"Adding missing FROM-clause entry%s for table %s",
pstate->parentParseState != NULL ? " in subquery" : "", pstate->parentParseState != NULL ? " in subquery" : "",
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.31 1999/09/29 18:16:04 momjian Exp $ * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.32 1999/10/07 04:23:12 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -144,7 +144,7 @@ colnameRangeTableEntry(ParseState *pstate, char *colname) ...@@ -144,7 +144,7 @@ colnameRangeTableEntry(ParseState *pstate, char *colname)
{ {
RangeTblEntry *rte = lfirst(et); RangeTblEntry *rte = lfirst(et);
/* only entries on outer(non-function?) scope */ /* only consider RTEs mentioned in FROM or UPDATE/DELETE */
if (!rte->inFromCl && rte != pstate->p_target_rangetblentry) if (!rte->inFromCl && rte != pstate->p_target_rangetblentry)
continue; continue;
...@@ -178,45 +178,51 @@ addRangeTableEntry(ParseState *pstate, ...@@ -178,45 +178,51 @@ addRangeTableEntry(ParseState *pstate,
char *relname, char *relname,
char *refname, char *refname,
bool inh, bool inh,
bool inFromCl) bool inFromCl,
bool inJoinSet)
{ {
Relation relation; Relation relation;
RangeTblEntry *rte = makeNode(RangeTblEntry); RangeTblEntry *rte;
int sublevels_up; int sublevels_up;
if (pstate != NULL) if (pstate != NULL)
{ {
if (refnameRangeTablePosn(pstate, refname, &sublevels_up) != 0 && int rt_index = refnameRangeTablePosn(pstate, refname,
(!inFromCl || sublevels_up == 0)) &sublevels_up);
if (rt_index != 0 && (!inFromCl || sublevels_up == 0))
{ {
if (!strcmp(refname, "*CURRENT*") || !strcmp(refname, "*NEW*")) if (!strcmp(refname, "*CURRENT*") || !strcmp(refname, "*NEW*"))
{
int rt_index = refnameRangeTablePosn(pstate, refname, &sublevels_up);
return (RangeTblEntry *) nth(rt_index - 1, pstate->p_rtable); return (RangeTblEntry *) nth(rt_index - 1, pstate->p_rtable);
}
elog(ERROR, "Table name '%s' specified more than once", refname); elog(ERROR, "Table name '%s' specified more than once", refname);
} }
} }
rte = makeNode(RangeTblEntry);
rte->relname = pstrdup(relname); rte->relname = pstrdup(relname);
rte->refname = pstrdup(refname); rte->refname = pstrdup(refname);
/* Get the rel's OID. This access also ensures that we have an
* up-to-date relcache entry for the rel. We don't need to keep
* it open, however.
*/
relation = heap_openr(relname, AccessShareLock); relation = heap_openr(relname, AccessShareLock);
rte->relid = RelationGetRelid(relation); rte->relid = RelationGetRelid(relation);
heap_close(relation, AccessShareLock); heap_close(relation, AccessShareLock);
/* /*
* Flags - zero or more from inheritance,union,version or recursive * Flags: this RTE should be expanded to include descendant tables,
* (transitive closure) [we don't support them all -- ay 9/94 ] * this RTE is in the FROM clause, this RTE should be included in
* the planner's final join.
*/ */
rte->inh = inh; rte->inh = inh;
/* RelOID */
rte->inFromCl = inFromCl; rte->inFromCl = inFromCl;
rte->inJoinSet = inJoinSet;
rte->skipAcl = false; /* always starts out false */
/* /*
* close the relation we're done with it for now. * Add completed RTE to range table list.
*/ */
if (pstate != NULL) if (pstate != NULL)
pstate->p_rtable = lappend(pstate->p_rtable, rte); pstate->p_rtable = lappend(pstate->p_rtable, rte);
...@@ -240,7 +246,8 @@ expandAll(ParseState *pstate, char *relname, char *refname, int *this_resno) ...@@ -240,7 +246,8 @@ expandAll(ParseState *pstate, char *relname, char *refname, int *this_resno)
rte = refnameRangeTableEntry(pstate, refname); rte = refnameRangeTableEntry(pstate, refname);
if (rte == NULL) if (rte == NULL)
{ {
rte = addRangeTableEntry(pstate, relname, refname, FALSE, FALSE); rte = addRangeTableEntry(pstate, relname, refname,
FALSE, FALSE, TRUE);
#ifdef WARN_FROM #ifdef WARN_FROM
elog(NOTICE,"Adding missing FROM-clause entry%s for table %s", elog(NOTICE,"Adding missing FROM-clause entry%s for table %s",
pstate->parentParseState != NULL ? " in subquery" : "", pstate->parentParseState != NULL ? " in subquery" : "",
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.59 1999/10/02 04:42:04 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.60 1999/10/07 04:23:15 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -818,12 +818,13 @@ ApplyRetrieveRule(Query *parsetree, ...@@ -818,12 +818,13 @@ ApplyRetrieveRule(Query *parsetree,
int rt_index, int rt_index,
int relation_level, int relation_level,
Relation relation, Relation relation,
bool relWasInJoinSet,
int *modified) int *modified)
{ {
Query *rule_action = NULL; Query *rule_action = NULL;
Node *rule_qual; Node *rule_qual;
List *rtable, List *rtable,
*rt, *addedrtable,
*l; *l;
int nothing, int nothing,
rt_length; rt_length;
...@@ -844,19 +845,23 @@ ApplyRetrieveRule(Query *parsetree, ...@@ -844,19 +845,23 @@ ApplyRetrieveRule(Query *parsetree,
nothing = TRUE; nothing = TRUE;
rtable = copyObject(parsetree->rtable); rtable = copyObject(parsetree->rtable);
foreach(rt, rtable) rt_length = length(rtable); /* original length, not counting rule */
{
RangeTblEntry *rte = lfirst(rt);
/* addedrtable = copyObject(rule_action->rtable);
* this is to prevent add_missing_vars_to_base_rels() from adding
* a bogus entry to the new target list. /* If the original rel wasn't in the join set, none of its spawn is.
*/ * If it was, then leave the spawn's flags as they are.
rte->inFromCl = false; */
if (! relWasInJoinSet)
{
foreach(l, addedrtable)
{
RangeTblEntry *rte = lfirst(l);
rte->inJoinSet = false;
}
} }
rt_length = length(rtable);
rtable = nconc(rtable, copyObject(rule_action->rtable)); rtable = nconc(rtable, addedrtable);
parsetree->rtable = rtable; parsetree->rtable = rtable;
/* FOR UPDATE of view... */ /* FOR UPDATE of view... */
...@@ -1006,10 +1011,14 @@ fireRIRrules(Query *parsetree) ...@@ -1006,10 +1011,14 @@ fireRIRrules(Query *parsetree)
RuleLock *rules; RuleLock *rules;
RewriteRule *rule; RewriteRule *rule;
RewriteRule RIRonly; RewriteRule RIRonly;
bool relWasInJoinSet;
int modified = false; int modified = false;
int i; int i;
List *l; List *l;
/* don't try to convert this into a foreach loop, because
* rtable list can get changed each time through...
*/
rt_index = 0; rt_index = 0;
while (rt_index < length(parsetree->rtable)) while (rt_index < length(parsetree->rtable))
{ {
...@@ -1017,46 +1026,57 @@ fireRIRrules(Query *parsetree) ...@@ -1017,46 +1026,57 @@ fireRIRrules(Query *parsetree)
rte = nth(rt_index - 1, parsetree->rtable); rte = nth(rt_index - 1, parsetree->rtable);
if (!rangeTableEntry_used((Node *) parsetree, rt_index, 0)) /*
* If the table is not one named in the original FROM clause
* then it must be referenced in the query, or we ignore it.
* This prevents infinite expansion loop due to new rtable
* entries inserted by expansion of a rule.
*/
if (! rte->inFromCl && rt_index != parsetree->resultRelation &&
! rangeTableEntry_used((Node *) parsetree, rt_index, 0))
{ {
/* Make sure the planner ignores it too... */
/* rte->inJoinSet = false;
* Unused range table entries must not be marked as coming
* from a clause. Otherwise the planner will generate joins
* over relations that in fact shouldn't be scanned at all and
* the result will contain duplicates
*
* Jan
*
*/
rte->inFromCl = FALSE;
continue; continue;
} }
rel = heap_openr(rte->relname, AccessShareLock); rel = heap_openr(rte->relname, AccessShareLock);
if (rel->rd_rules == NULL) rules = rel->rd_rules;
if (rules == NULL)
{ {
heap_close(rel, AccessShareLock); heap_close(rel, AccessShareLock);
continue; continue;
} }
rules = rel->rd_rules; relWasInJoinSet = rte->inJoinSet; /* save before possibly clearing */
locks = NIL;
/* /*
* Collect the RIR rules that we must apply * Collect the RIR rules that we must apply
*/ */
locks = NIL;
for (i = 0; i < rules->numLocks; i++) for (i = 0; i < rules->numLocks; i++)
{ {
rule = rules->rules[i]; rule = rules->rules[i];
if (rule->event != CMD_SELECT) if (rule->event != CMD_SELECT)
continue; continue;
if (rule->attrno > 0 && if (rule->attrno > 0)
!attribute_used((Node *) parsetree, {
rt_index, /* per-attr rule; do we need it? */
rule->attrno, 0)) if (! attribute_used((Node *) parsetree,
continue; rt_index,
rule->attrno, 0))
continue;
}
else
{
/* Rel-wide ON SELECT DO INSTEAD means this is a view.
* Remove the view from the planner's join target set,
* or we'll get no rows out because view itself is empty!
*/
if (rule->isInstead)
rte->inJoinSet = false;
}
locks = lappend(locks, rule); locks = lappend(locks, rule);
} }
...@@ -1083,6 +1103,7 @@ fireRIRrules(Query *parsetree) ...@@ -1083,6 +1103,7 @@ fireRIRrules(Query *parsetree)
rt_index, rt_index,
RIRonly.attrno == -1, RIRonly.attrno == -1,
rel, rel,
relWasInJoinSet,
&modified); &modified);
} }
...@@ -2012,10 +2033,10 @@ Except_Intersect_Rewrite(Query *parsetree) ...@@ -2012,10 +2033,10 @@ Except_Intersect_Rewrite(Query *parsetree)
* If the Select Query node has aggregates in use add all the * If the Select Query node has aggregates in use add all the
* subselects to the HAVING qual else to the WHERE qual * subselects to the HAVING qual else to the WHERE qual
*/ */
if (intersect_node->hasAggs == false) if (intersect_node->hasAggs)
AddQual(intersect_node, (Node *) n);
else
AddHavingQual(intersect_node, (Node *) n); AddHavingQual(intersect_node, (Node *) n);
else
AddQual(intersect_node, (Node *) n);
/* Now we got sublinks */ /* Now we got sublinks */
intersect_node->hasSubLinks = true; intersect_node->hasSubLinks = true;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Copyright (c) 1994, Regents of the University of California * Copyright (c) 1994, Regents of the University of California
* *
* $Id: parsenodes.h,v 1.83 1999/10/03 23:55:36 tgl Exp $ * $Id: parsenodes.h,v 1.84 1999/10/07 04:23:17 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -45,7 +45,7 @@ typedef struct Query ...@@ -45,7 +45,7 @@ typedef struct Query
bool isBinary; /* binary portal? */ bool isBinary; /* binary portal? */
bool isTemp; /* is 'into' a temp table? */ bool isTemp; /* is 'into' a temp table? */
bool unionall; /* union without unique sort */ bool unionall; /* union without unique sort */
bool hasAggs; /* has aggregates in target list */ bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasSubLinks; /* has subquery SubLink */ bool hasSubLinks; /* has subquery SubLink */
List *rtable; /* list of range table entries */ List *rtable; /* list of range table entries */
...@@ -968,30 +968,48 @@ typedef struct TargetEntry ...@@ -968,30 +968,48 @@ typedef struct TargetEntry
NodeTag type; NodeTag type;
Resdom *resdom; /* fjoin overload this to be a list?? */ Resdom *resdom; /* fjoin overload this to be a list?? */
Fjoin *fjoin; Fjoin *fjoin;
Node *expr; /* can be a list too */ Node *expr;
} TargetEntry; } TargetEntry;
/* /*--------------------
* RangeTblEntry - * RangeTblEntry -
* used in range tables. Some of the following are only used in one of * A range table is a List of RangeTblEntry nodes.
*
* Some of the following are only used in one of
* the parsing, optimizing, execution stages. * the parsing, optimizing, execution stages.
* *
* inFromCl marks those range variables that are listed in the from clause. * inFromCl marks those range variables that are listed in the FROM clause.
* In SQL, the targetlist can only refer to range variables listed in the * In SQL, the query can only refer to range variables listed in the
* from clause but POSTQUEL allows you to refer to tables not specified, in * FROM clause, but POSTQUEL allows you to refer to tables not listed,
* which case a range table entry will be generated. We use POSTQUEL * in which case a range table entry will be generated. We still support
* semantics which is more powerful. However, we need SQL semantics in * this POSTQUEL feature, although there is some doubt whether it's
* some cases (eg. when expanding a '*') * convenient or merely confusing. The flag is needed since an
* implicitly-added RTE shouldn't change the namespace for unqualified
* column names processed later, and it also shouldn't affect the
* expansion of '*'.
*
* inJoinSet marks those range variables that the planner should join
* over even if they aren't explicitly referred to in the query. For
* example, "SELECT COUNT(1) FROM tx" should produce the number of rows
* in tx. A more subtle example uses a POSTQUEL implicit RTE:
* SELECT COUNT(1) FROM tx WHERE TRUE OR (tx.f1 = ty.f2)
* Here we should get the product of the sizes of tx and ty. However,
* the query optimizer can simplify the WHERE clause to "TRUE", so
* ty will no longer be referred to explicitly; without a flag forcing
* it to be included in the join, we will get the wrong answer. So,
* a POSTQUEL implicit RTE must be marked inJoinSet but not inFromCl.
*--------------------
*/ */
typedef struct RangeTblEntry typedef struct RangeTblEntry
{ {
NodeTag type; NodeTag type;
char *relname; /* real name of the relation */ char *relname; /* real name of the relation */
char *refname; /* the reference name (specified in the char *refname; /* the reference name (as specified in the
* from clause) */ * FROM clause) */
Oid relid; Oid relid; /* OID of the relation */
bool inh; /* inheritance? */ bool inh; /* inheritance requested? */
bool inFromCl; /* comes from From Clause */ bool inFromCl; /* present in FROM clause */
bool inJoinSet; /* planner must include this rel */
bool skipAcl; /* skip ACL check in executor */ bool skipAcl; /* skip ACL check in executor */
} RangeTblEntry; } RangeTblEntry;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Copyright (c) 1994, Regents of the University of California * Copyright (c) 1994, Regents of the University of California
* *
* $Id: planmain.h,v 1.33 1999/08/22 23:56:43 tgl Exp $ * $Id: planmain.h,v 1.34 1999/10/07 04:23:19 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -19,9 +19,7 @@ ...@@ -19,9 +19,7 @@
/* /*
* prototypes for plan/planmain.c * prototypes for plan/planmain.c
*/ */
extern Plan *query_planner(Query *root, extern Plan *query_planner(Query *root, List *tlist, List *qual);
int command_type, List *tlist, List *qual);
/* /*
* prototypes for plan/createplan.c * prototypes for plan/createplan.c
...@@ -42,8 +40,8 @@ extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan); ...@@ -42,8 +40,8 @@ extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
*/ */
extern void make_var_only_tlist(Query *root, List *tlist); extern void make_var_only_tlist(Query *root, List *tlist);
extern void add_restrict_and_join_to_rels(Query *root, List *clauses); extern void add_restrict_and_join_to_rels(Query *root, List *clauses);
extern void add_missing_rels_to_query(Query *root);
extern void set_joininfo_mergeable_hashable(List *rel_list); extern void set_joininfo_mergeable_hashable(List *rel_list);
extern void add_missing_vars_to_tlist(Query *root, List *tlist);
/* /*
* prototypes for plan/setrefs.c * prototypes for plan/setrefs.c
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Copyright (c) 1994, Regents of the University of California * Copyright (c) 1994, Regents of the University of California
* *
* $Id: parse_relation.h,v 1.12 1999/07/19 00:26:17 tgl Exp $ * $Id: parse_relation.h,v 1.13 1999/10/07 04:23:22 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -23,7 +23,8 @@ extern RangeTblEntry *addRangeTableEntry(ParseState *pstate, ...@@ -23,7 +23,8 @@ extern RangeTblEntry *addRangeTableEntry(ParseState *pstate,
char *relname, char *relname,
char *refname, char *refname,
bool inh, bool inh,
bool inFromCl); bool inFromCl,
bool inJoinSet);
extern List *expandAll(ParseState *pstate, char *relname, char *refname, extern List *expandAll(ParseState *pstate, char *relname, char *refname,
int *this_resno); int *this_resno);
extern int attnameAttNum(Relation rd, char *a); extern int attnameAttNum(Relation rd, char *a);
......
...@@ -1075,9 +1075,9 @@ pg_user |SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecr ...@@ -1075,9 +1075,9 @@ pg_user |SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecr
pg_views |SELECT c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.relname) AS definition FROM pg_class c WHERE (c.relhasrules AND (EXISTS (SELECT r.rulename FROM pg_rewrite r WHERE ((r.ev_class = c.oid) AND (r.ev_type = '1'::char))))); pg_views |SELECT c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.relname) AS definition FROM pg_class c WHERE (c.relhasrules AND (EXISTS (SELECT r.rulename FROM pg_rewrite r WHERE ((r.ev_class = c.oid) AND (r.ev_type = '1'::char)))));
rtest_v1 |SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1; rtest_v1 |SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1;
rtest_vcomp |SELECT x.part, (x.size * y.factor) AS size_in_cm FROM rtest_comp x, rtest_unitfact y WHERE (x.unit = y.unit); rtest_vcomp |SELECT x.part, (x.size * y.factor) AS size_in_cm FROM rtest_comp x, rtest_unitfact y WHERE (x.unit = y.unit);
rtest_vview1 |SELECT x.a, x.b FROM rtest_view1 x WHERE (0 < (SELECT count(y.a) AS count FROM rtest_view2 y WHERE (y.a = x.a))); rtest_vview1 |SELECT x.a, x.b FROM rtest_view1 x WHERE (0 < (SELECT count(1) AS count FROM rtest_view2 y WHERE (y.a = x.a)));
rtest_vview2 |SELECT rtest_view1.a, rtest_view1.b FROM rtest_view1 WHERE rtest_view1.v; rtest_vview2 |SELECT rtest_view1.a, rtest_view1.b FROM rtest_view1 WHERE rtest_view1.v;
rtest_vview3 |SELECT x.a, x.b FROM rtest_vview2 x WHERE (0 < (SELECT count(y.a) AS count FROM rtest_view2 y WHERE (y.a = x.a))); rtest_vview3 |SELECT x.a, x.b FROM rtest_vview2 x WHERE (0 < (SELECT count(1) AS count FROM rtest_view2 y WHERE (y.a = x.a)));
rtest_vview4 |SELECT x.a, x.b, count(y.a) AS refcount FROM rtest_view1 x, rtest_view2 y WHERE (x.a = y.a) GROUP BY x.a, x.b; rtest_vview4 |SELECT x.a, x.b, count(y.a) AS refcount FROM rtest_view1 x, rtest_view2 y WHERE (x.a = y.a) GROUP BY x.a, x.b;
rtest_vview5 |SELECT rtest_view1.a, rtest_view1.b, rtest_viewfunc1(rtest_view1.a) AS refcount FROM rtest_view1; rtest_vview5 |SELECT rtest_view1.a, rtest_view1.b, rtest_viewfunc1(rtest_view1.a) AS refcount FROM rtest_view1;
shoe |SELECT sh.shoename, sh.sh_avail, sh.slcolor, sh.slminlen, (sh.slminlen * un.un_fact) AS slminlen_cm, sh.slmaxlen, (sh.slmaxlen * un.un_fact) AS slmaxlen_cm, sh.slunit FROM shoe_data sh, unit un WHERE (sh.slunit = un.un_name); shoe |SELECT sh.shoename, sh.sh_avail, sh.slcolor, sh.slminlen, (sh.slminlen * un.un_fact) AS slminlen_cm, sh.slmaxlen, (sh.slmaxlen * un.un_fact) AS slmaxlen_cm, sh.slunit FROM shoe_data sh, unit un WHERE (sh.slunit = un.un_name);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment