diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index a1f26d9fc1..139c95c591 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -5,7 +5,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: execAmi.c,v 1.28 1998/12/14 08:11:02 scrappy Exp $ + * $Id: execAmi.c,v 1.29 1999/01/18 00:09:45 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -39,6 +39,9 @@ #include "executor/nodeNestloop.h" #include "executor/nodeHashjoin.h" #include "executor/nodeHash.h" +/***S*I***/ +#include "executor/nodeGroup.h" + #include "executor/nodeAgg.h" #include "executor/nodeGroup.h" #include "executor/nodeResult.h" diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 0eed756319..dbb86bb0f7 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -491,7 +491,10 @@ ExecAgg(Agg *node) * As long as the retrieved group does not match the * qualifications it is ignored and the next group is fetched */ - qual_result = ExecQual(fix_opids(node->plan.qual), econtext); + if(node->plan.qual != NULL){ + qual_result = ExecQual(fix_opids(node->plan.qual), econtext); + } + if (oneTuple) pfree(oneTuple); } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 9d6d3e148a..5577bbb290 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -5,7 +5,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: outfuncs.c,v 1.58 1998/12/20 07:13:36 scrappy Exp $ + * $Id: outfuncs.c,v 1.59 1999/01/18 00:09:45 momjian Exp $ * * NOTES * Every (plan) node in POSTGRES has an associated "out" routine which @@ -227,6 +227,9 @@ _outQuery(StringInfo str, Query *node) node->hasSubLinks ? "true" : "false"); _outNode(str, node->unionClause); + appendStringInfo(str, " :intersectClause "); + _outNode(str, node->intersectClause); + appendStringInfo(str, " :limitOffset "); _outNode(str, node->limitOffset); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index a6650efecf..edcfdd9d26 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.40 1998/12/14 00:01:47 thomas Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.41 1999/01/18 00:09:46 momjian Exp $ * * NOTES * Most of the read functions for plan nodes are tested. (In fact, they @@ -163,6 +163,11 @@ _readQuery() token = lsptok(NULL, &length); /* skip :unionClause */ local_node->unionClause = nodeRead(true); + /***S*I***/ + token = lsptok(NULL, &length); /* skip :intersectClause */ + local_node->intersectClause = nodeRead(true); + + token = lsptok(NULL, &length); /* skip :limitOffset */ local_node->limitOffset = nodeRead(true); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 9cf0746145..ec4597961a 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.35 1998/09/09 03:48:01 vadim Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.36 1999/01/18 00:09:47 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -95,20 +95,26 @@ Plan * union_planner(Query *parse) { List *tlist = parse->targetList; - int tlist_len = length(tlist); - List *rangetable = parse->rtable; - Plan *result_plan = (Plan *) NULL; - Index rt_index; + /***S*H***/ + /* copy the original tlist, we will need the original one + * for the AGG node later on */ + List *new_tlist = new_unsorted_tlist(tlist); + + List *rangetable = parse->rtable; + + Plan *result_plan = (Plan *) NULL; + + Index rt_index; if (parse->unionClause) { - result_plan = (Plan *) plan_union_queries(parse); - /* XXX do we need to do this? bjm 12/19/97 */ - tlist = preprocess_targetlist(tlist, - parse->commandType, - parse->resultRelation, - parse->rtable); + result_plan = (Plan *) plan_union_queries(parse); + /* XXX do we need to do this? bjm 12/19/97 */ + tlist = preprocess_targetlist(tlist, + parse->commandType, + parse->resultRelation, + parse->rtable); } else if ((rt_index = first_inherit_rt_entry(rangetable)) != -1) @@ -116,47 +122,65 @@ union_planner(Query *parse) result_plan = (Plan *) plan_inherit_queries(parse, rt_index); /* XXX do we need to do this? bjm 12/19/97 */ tlist = preprocess_targetlist(tlist, - parse->commandType, - parse->resultRelation, - parse->rtable); + parse->commandType, + parse->resultRelation, + parse->rtable); } else { - List **vpm = NULL; - - /* - * check_having_qual_for_vars takes the havingQual and the tlist - * as arguments and recursively scans the havingQual for VAR nodes - * that are not contained in tlist yet. If so, it creates a new entry - * and attaches it to the tlist. Latter, we use tlist_len to - * truncate tlist - ie restore actual tlist... - */ - if (parse->hasAggs) + List **vpm = NULL; + + /***S*H***/ + /* This is only necessary if aggregates are in use in queries like: + * SELECT sid + * FROM part + * GROUP BY sid + * HAVING MIN(pid) > 1; (pid is used but never selected for!!!) + * because the function 'query_planner' creates the plan for the lefttree + * of the 'GROUP' node and returns only those attributes contained in 'tlist'. + * The original 'tlist' contains only 'sid' here and that's why we have to + * to extend it to attributes which are not selected but are used in the + * havingQual. */ + + /* 'check_having_qual_for_vars' takes the havingQual and the actual 'tlist' + * as arguments and recursively scans the havingQual for attributes + * (VAR nodes) that are not contained in 'tlist' yet. If so, it creates + * a new entry and attaches it to the list 'new_tlist' (consisting of the + * VAR node and the RESDOM node as usual with tlists :-) ) */ + if (parse->hasAggs) + { + if (parse->havingQual != NULL) { - if (parse->havingQual != NULL) - tlist = check_having_qual_for_vars(parse->havingQual, tlist); + new_tlist = check_having_qual_for_vars(parse->havingQual,new_tlist); } - - tlist = preprocess_targetlist(tlist, - parse->commandType, - parse->resultRelation, - parse->rtable); - - if (parse->rtable != NULL) - { - vpm = (List **) palloc(length(parse->rtable) * sizeof(List *)); - memset(vpm, 0, length(parse->rtable) * sizeof(List *)); - } - PlannerVarParam = lcons(vpm, PlannerVarParam); - result_plan = query_planner(parse, - parse->commandType, - tlist, - (List *) parse->qual); - PlannerVarParam = lnext(PlannerVarParam); - if (vpm != NULL) - pfree(vpm); + } + + new_tlist = preprocess_targetlist(new_tlist, + parse->commandType, + parse->resultRelation, + parse->rtable); + + /* Here starts the original (pre having) code */ + tlist = preprocess_targetlist(tlist, + parse->commandType, + parse->resultRelation, + parse->rtable); + + if (parse->rtable != NULL) + { + vpm = (List **) palloc(length(parse->rtable) * sizeof(List *)); + memset(vpm, 0, length(parse->rtable) * sizeof(List *)); + } + PlannerVarParam = lcons(vpm, PlannerVarParam); + result_plan = query_planner(parse, + parse->commandType, + new_tlist, + (List *) parse->qual); + PlannerVarParam = lnext(PlannerVarParam); + if (vpm != NULL) + pfree(vpm); } - + /* * If we have a GROUP BY clause, insert a group node (with the * appropriate sort node.) @@ -173,8 +197,10 @@ union_planner(Query *parse) */ tuplePerGroup = parse->hasAggs; + /***S*H***/ + /* Use 'new_tlist' instead of 'tlist' */ result_plan = - make_groupPlan(&tlist, + make_groupPlan(&new_tlist, tuplePerGroup, parse->groupClause, result_plan); @@ -185,6 +211,11 @@ union_planner(Query *parse) */ if (parse->hasAggs) { + int old_length=0, new_length=0; + + /* Create the AGG node but use 'tlist' not 'new_tlist' as target list because we + * don't want the additional attributes (only used for the havingQual, see above) + * to show up in the result */ result_plan = (Plan *) make_agg(tlist, result_plan); /* @@ -192,78 +223,74 @@ union_planner(Query *parse) * the result tuple of the subplans. */ ((Agg *) result_plan)->aggs = - set_agg_tlist_references((Agg *) result_plan); + set_agg_tlist_references((Agg *) result_plan); - if (parse->havingQual != NULL) - { - List *clause; - List **vpm = NULL; + /***S*H***/ + if(parse->havingQual!=NULL) + { + List *clause; + List **vpm = NULL; + + + /* stuff copied from above to handle the use of attributes from outside + * in subselects */ - /* - * Restore target list: get rid of Vars added for havingQual. - * Assumption: tlist_len > 0... - */ - { - List *l; - int tlen = 0; + if (parse->rtable != NULL) + { + vpm = (List **) palloc(length(parse->rtable) * sizeof(List *)); + memset(vpm, 0, length(parse->rtable) * sizeof(List *)); + } + PlannerVarParam = lcons(vpm, PlannerVarParam); + + + /* convert the havingQual to conjunctive normal form (cnf) */ + (List *) parse->havingQual=cnfify((Expr *)(Node *) parse->havingQual,true); + + /* There is a subselect in the havingQual, so we have to process it + * using the same function as for a subselect in 'where' */ + if (parse->hasSubLinks) + { + (List *) parse->havingQual = + (List *) SS_process_sublinks((Node *) parse->havingQual); + } + + + /* Calculate the opfids from the opnos (=select the correct functions for + * the used VAR datatypes) */ + (List *) parse->havingQual=fix_opids((List *) parse->havingQual); + + ((Agg *) result_plan)->plan.qual=(List *) parse->havingQual; + + /* Check every clause of the havingQual for aggregates used and append + * them to result_plan->aggs */ + foreach(clause, ((Agg *) result_plan)->plan.qual) + { + /* Make sure there are aggregates in the havingQual + * if so, the list must be longer after check_having_qual_for_aggs */ + old_length=length(((Agg *) result_plan)->aggs); - foreach (l, ((Agg *) result_plan)->plan.targetlist) - { - if (++tlen == tlist_len) - break; - } - lnext(l) = NIL; - } - - /* - * stuff copied from above to handle the use of attributes - * from outside in subselects - */ - - if (parse->rtable != NULL) - { - vpm = (List **) palloc(length(parse->rtable) * sizeof(List *)); - memset(vpm, 0, length(parse->rtable) * sizeof(List *)); - } - PlannerVarParam = lcons(vpm, PlannerVarParam); - - /* - * There is a subselect in the havingQual, so we have to - * process it using the same function as for a subselect in - * 'where' - */ - if (parse->hasSubLinks) - parse->havingQual = SS_process_sublinks((Node *) parse->havingQual); - - /* convert the havingQual to conjunctive normal form (cnf) */ - parse->havingQual = (Node *) cnfify((Expr *) (Node *) parse->havingQual, true); - - /* - * Calculate the opfids from the opnos (=select the correct - * functions for the used VAR datatypes) - */ - parse->havingQual = (Node *) fix_opids((List *) parse->havingQual); - - ((Agg *) result_plan)->plan.qual = (List *) parse->havingQual; - - /* - * Check every clause of the havingQual for aggregates used - * and append them to result_plan->aggs - */ - foreach(clause, ((Agg *) result_plan)->plan.qual) - { - ((Agg *) result_plan)->aggs = nconc(((Agg *) result_plan)->aggs, - check_having_qual_for_aggs((Node *) lfirst(clause), - ((Agg *) result_plan)->plan.lefttree->targetlist, - ((List *) parse->groupClause))); - } - PlannerVarParam = lnext(PlannerVarParam); - if (vpm != NULL) - pfree(vpm); - } - } + ((Agg *) result_plan)->aggs = nconc(((Agg *) result_plan)->aggs, + check_having_qual_for_aggs((Node *) lfirst(clause), + ((Agg *) result_plan)->plan.lefttree->targetlist, + ((List *) parse->groupClause))); + /* Have a look at the length of the returned list. If there is no + * difference, no aggregates have been found and that means, that + * the Qual belongs to the where clause */ + if (((new_length=length(((Agg *) result_plan)->aggs)) == old_length) || + (new_length == 0)) + { + elog(ERROR,"This could have been done in a where clause!!"); + return (Plan *)NIL; + } + } + PlannerVarParam = lnext(PlannerVarParam); + if (vpm != NULL) + pfree(vpm); + } + } + /* * For now, before we hand back the plan, check to see if there is a * user-specified sort that needs to be done. Eventually, this will @@ -277,14 +304,14 @@ union_planner(Query *parse) { Plan *sortplan = make_sortplan(tlist, parse->sortClause, result_plan); - return (Plan *) make_unique(tlist, sortplan, parse->uniqueFlag); + return ((Plan *) make_unique(tlist, sortplan, parse->uniqueFlag)); } else { if (parse->sortClause) - return make_sortplan(tlist, parse->sortClause, result_plan); + return (make_sortplan(tlist, parse->sortClause, result_plan)); else - return (Plan *) result_plan; + return ((Plan *) result_plan); } } diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 3d3ad51c31..dc04e3c5c3 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.29 1998/12/14 00:02:10 thomas Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.30 1999/01/18 00:09:48 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -923,173 +923,195 @@ del_agg_clause(Node *clause) return NULL; } - +/***S*H***/ /* check_having_qual_for_vars takes the the havingQual and the actual targetlist as arguments * and recursively scans the havingQual for attributes that are not included in the targetlist * yet. Attributes contained in the havingQual but not in the targetlist show up with queries - * like: - * SELECT sid + * like: + * SELECT sid * FROM part * GROUP BY sid - * HAVING MIN(pid) > 1; (pid is used but never selected for!!!). + * HAVING MIN(pid) > 1; (pid is used but never selected for!!!). * To be able to handle queries like that correctly we have to extend the actual targetlist - * (which will be the one used for the GROUP node later on) by these attributes. */ + * (which will be the one used for the GROUP node later on) by these attributes. */ List * check_having_qual_for_vars(Node *clause, List *targetlist_so_far) { - List *t; + List *t; - if (IsA(clause, Var)) + if (IsA(clause, Var)) + { + RelOptInfo tmp_rel; + + + tmp_rel.targetlist = targetlist_so_far; + + /* + * Ha! A Var node! + */ + + /* Check if the VAR is already contained in the targetlist */ + if (tlist_member((Var *)clause, (List *)targetlist_so_far) == NULL) { - RelOptInfo tmp_rel; - - - tmp_rel.targetlist = targetlist_so_far; - - /* - * Ha! A Var node! - */ - - /* Check if the VAR is already contained in the targetlist */ - if (tlist_member((Var *) clause, (List *) targetlist_so_far) == NULL) - add_tl_element(&tmp_rel, (Var *) clause); - - return tmp_rel.targetlist; - } - - else if (is_funcclause(clause) || not_clause(clause) || - or_clause(clause) || and_clause(clause)) + add_tl_element(&tmp_rel, (Var *)clause); + } + + return tmp_rel.targetlist; + } + + else if (is_funcclause(clause) || not_clause(clause) || + or_clause(clause) || and_clause(clause)) + { + + /* + * This is a function. Recursively call this routine for its + * arguments... + */ + foreach(t, ((Expr *) clause)->args) { - - /* - * This is a function. Recursively call this routine for its - * arguments... - */ - foreach(t, ((Expr *) clause)->args) - targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far); - return targetlist_so_far; + targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far); } - else if (IsA(clause, Aggreg)) + return targetlist_so_far; + } + else if (IsA(clause, Aggreg)) + { + targetlist_so_far = + check_having_qual_for_vars(((Aggreg *) clause)->target, targetlist_so_far); + return targetlist_so_far; + } + else if (IsA(clause, ArrayRef)) + { + ArrayRef *aref = (ArrayRef *) clause; + + /* + * This is an arrayref. Recursively call this routine for its + * expression and its index expression... + */ + foreach(t, aref->refupperindexpr) { - targetlist_so_far = - check_having_qual_for_vars(((Aggreg *) clause)->target, targetlist_so_far); - return targetlist_so_far; + targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far); } - else if (IsA(clause, ArrayRef)) + foreach(t, aref->reflowerindexpr) { - ArrayRef *aref = (ArrayRef *) clause; - - /* - * This is an arrayref. Recursively call this routine for its - * expression and its index expression... - */ - foreach(t, aref->refupperindexpr) - targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far); - foreach(t, aref->reflowerindexpr) - targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far); - targetlist_so_far = check_having_qual_for_vars(aref->refexpr, targetlist_so_far); - targetlist_so_far = check_having_qual_for_vars(aref->refassgnexpr, targetlist_so_far); - - return targetlist_so_far; + targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far); } - else if (is_opclause(clause)) + targetlist_so_far = check_having_qual_for_vars(aref->refexpr, targetlist_so_far); + targetlist_so_far = check_having_qual_for_vars(aref->refassgnexpr, targetlist_so_far); + + return targetlist_so_far; + } + else if (is_opclause(clause)) + { + + /* + * This is an operator. Recursively call this routine for both its + * left and right operands + */ + Node *left = (Node *) get_leftop((Expr *) clause); + Node *right = (Node *) get_rightop((Expr *) clause); + + if (left != (Node *) NULL) + targetlist_so_far = check_having_qual_for_vars(left, targetlist_so_far); + if (right != (Node *) NULL) + targetlist_so_far = check_having_qual_for_vars(right, targetlist_so_far); + + return targetlist_so_far; + } + else if (IsA(clause, Param) || IsA(clause, Const)) + { + /* do nothing! */ + return targetlist_so_far; + } + /* If we get to a sublink, then we only have to check the lefthand side of the expression + * to see if there are any additional VARs */ + else if (IsA(clause, SubLink)) + { + foreach(t,((List *)((SubLink *)clause)->lefthand)) { - - /* - * This is an operator. Recursively call this routine for both its - * left and right operands - */ - Node *left = (Node *) get_leftop((Expr *) clause); - Node *right = (Node *) get_rightop((Expr *) clause); - - if (left != (Node *) NULL) - targetlist_so_far = check_having_qual_for_vars(left, targetlist_so_far); - if (right != (Node *) NULL) - targetlist_so_far = check_having_qual_for_vars(right, targetlist_so_far); - - return targetlist_so_far; - } - else if (IsA(clause, Param) ||IsA(clause, Const)) - { - /* do nothing! */ - return targetlist_so_far; - } - - /* - * If we get to a sublink, then we only have to check the lefthand - * side of the expression to see if there are any additional VARs - */ - else if (IsA(clause, SubLink)) - { - foreach(t, ((List *) ((SubLink *) clause)->lefthand)) - targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far); - return targetlist_so_far; - } - else - { - - /* - * Ooops! we can not handle that! - */ - elog(ERROR, "check_having_qual_for_vars: Can not handle this having_qual! %d\n", - nodeTag(clause)); - return NIL; + targetlist_so_far = check_having_qual_for_vars(lfirst(t), targetlist_so_far); } + return targetlist_so_far; + } + else + { + /* + * Ooops! we can not handle that! + */ + elog(ERROR, "check_having_qual_for_vars: Can not handle this having_qual! %d\n", + nodeTag(clause)); + return NIL; + } } -/* check_having_qual_for_aggs takes the havingQual, the targetlist and the groupClause +/* check_having_qual_for_aggs takes the havingQual, the targetlist and the groupClause * as arguments and scans the havingQual recursively for aggregates. If an aggregate is - * found it is attached to a list and returned by the function. (All the returned lists + * found it is attached to a list and returned by the function. (All the returned lists * are concenated to result_plan->aggs in planner.c:union_planner() */ List * check_having_qual_for_aggs(Node *clause, List *subplanTargetList, List *groupClause) { - List *t, - *l1; + List *t, *l1; List *agg_list = NIL; - int contained_in_group_clause = 0; - + int contained_in_group_clause = 0; + if (IsA(clause, Var)) { - TargetEntry *subplanVar; + TargetEntry *subplanVar; + + /* + * Ha! A Var node! + */ + subplanVar = match_varid((Var *) clause, subplanTargetList); + + /* + * Change the varno & varattno fields of the var node to point to the resdom->resno + * fields of the subplan (lefttree) + */ + ((Var *) clause)->varattno = subplanVar->resdom->resno; - /* - * Ha! A Var node! - */ - subplanVar = match_varid((Var *) clause, subplanTargetList); - - /* - * Change the varno & varattno fields of the var node to point to - * the resdom->resno fields of the subplan (lefttree) - */ - ((Var *) clause)->varattno = subplanVar->resdom->resno; - - return NIL; + return NIL; } - else if (is_funcclause(clause) || not_clause(clause) || - or_clause(clause) || and_clause(clause)) + /***S*H***/ + else if (is_funcclause(clause) || not_clause(clause) || + or_clause(clause) || and_clause(clause)) { + int new_length=0, old_length=0; + /* * This is a function. Recursively call this routine for its * arguments... (i.e. for AND, OR, ... clauses!) */ foreach(t, ((Expr *) clause)->args) { - agg_list = nconc(agg_list, - check_having_qual_for_aggs(lfirst(t), subplanTargetList, - groupClause)); + old_length=length((List *)agg_list); + + agg_list = nconc(agg_list, + check_having_qual_for_aggs(lfirst(t), subplanTargetList, + groupClause)); + + /* The arguments of OR or AND clauses are comparisons or relations + * and because we are in the havingQual there must be at least one operand + * using an aggregate function. If so, we will find it and the length of the + * agg_list will be increased after the above call to + * check_having_qual_for_aggs. If there are no aggregates used, the query + * could have been formulated using the 'where' clause */ + if(((new_length=length((List *)agg_list)) == old_length) || (new_length == 0)) + { + elog(ERROR,"This could have been done in a where clause!!"); + return NIL; + } } return agg_list; } else if (IsA(clause, Aggreg)) { return lcons(clause, - check_having_qual_for_aggs(((Aggreg *) clause)->target, subplanTargetList, - groupClause)); + check_having_qual_for_aggs(((Aggreg *) clause)->target, subplanTargetList, + groupClause)); } else if (IsA(clause, ArrayRef)) { @@ -1102,21 +1124,21 @@ check_having_qual_for_aggs(Node *clause, List *subplanTargetList, List *groupCla foreach(t, aref->refupperindexpr) { agg_list = nconc(agg_list, - check_having_qual_for_aggs(lfirst(t), subplanTargetList, - groupClause)); + check_having_qual_for_aggs(lfirst(t), subplanTargetList, + groupClause)); } foreach(t, aref->reflowerindexpr) { agg_list = nconc(agg_list, - check_having_qual_for_aggs(lfirst(t), subplanTargetList, - groupClause)); + check_having_qual_for_aggs(lfirst(t), subplanTargetList, + groupClause)); } agg_list = nconc(agg_list, - check_having_qual_for_aggs(aref->refexpr, subplanTargetList, - groupClause)); + check_having_qual_for_aggs(aref->refexpr, subplanTargetList, + groupClause)); agg_list = nconc(agg_list, - check_having_qual_for_aggs(aref->refassgnexpr, subplanTargetList, - groupClause)); + check_having_qual_for_aggs(aref->refassgnexpr, subplanTargetList, + groupClause)); return agg_list; } @@ -1132,85 +1154,92 @@ check_having_qual_for_aggs(Node *clause, List *subplanTargetList, List *groupCla if (left != (Node *) NULL) agg_list = nconc(agg_list, - check_having_qual_for_aggs(left, subplanTargetList, - groupClause)); + check_having_qual_for_aggs(left, subplanTargetList, + groupClause)); if (right != (Node *) NULL) agg_list = nconc(agg_list, check_having_qual_for_aggs(right, subplanTargetList, - groupClause)); + groupClause)); return agg_list; } - else if (IsA(clause, Param) ||IsA(clause, Const)) + else if (IsA(clause, Param) || IsA(clause, Const)) { /* do nothing! */ return NIL; } - - /* - * This is for Sublinks which show up as EXPR nodes. All the other - * EXPR nodes (funcclauses, and_clauses, or_clauses) were caught above - */ + /* This is for Sublinks which show up as EXPR nodes. All the other EXPR nodes + * (funcclauses, and_clauses, or_clauses) were caught above */ else if (IsA(clause, Expr)) - { + { + /* Only the lefthand side of the sublink has to be checked for aggregates + * to be attached to result_plan->aggs (see planner.c:union_planner() ) + */ + foreach(t,((List *)((SubLink *)((SubPlan *) + ((Expr *)clause)->oper)->sublink)->lefthand)) + { + agg_list = + nconc(agg_list, + check_having_qual_for_aggs(lfirst(t), + subplanTargetList, groupClause)); + } - /* - * Only the lefthand side of the sublink has to be checked for - * aggregates to be attached to result_plan->aggs (see - * planner.c:union_planner() ) - */ - foreach(t, ((List *) ((SubLink *) ((SubPlan *) - ((Expr *) clause)->oper)->sublink)->lefthand)) - { - agg_list = - nconc(agg_list, - check_having_qual_for_aggs(lfirst(t), - subplanTargetList, groupClause)); + /* The first argument of ...->oper has also to be checked */ + { + List *tmp_ptr; + + foreach(tmp_ptr, ((SubLink *)((SubPlan *) + ((Expr *)clause)->oper)->sublink)->oper) + { + agg_list = + nconc(agg_list, + check_having_qual_for_aggs((Node *)lfirst(((Expr *) + lfirst(tmp_ptr))->args), + subplanTargetList, groupClause)); } + } + + /* All arguments to the Sublink node are attributes from outside used within + * the sublink. Here we have to check that only attributes that is grouped for + * are used! */ + foreach(t,((Expr *)clause)->args) + { + contained_in_group_clause = 0; - - /* - * All arguments to the Sublink node are attributes from outside - * used within the sublink. Here we have to check that only - * attributes that is grouped for are used! - */ - foreach(t, ((Expr *) clause)->args) - { - contained_in_group_clause = 0; - - foreach(l1, groupClause) - { - if (tlist_member(lfirst(t), lcons(((GroupClause *) lfirst(l1))->entry, NIL)) != - NULL) - contained_in_group_clause = 1; - } - - /* - * If the use of the attribute is allowed (i.e. it is in the - * groupClause) we have to adjust the varnos and varattnos - */ - if (contained_in_group_clause) - { - agg_list = - nconc(agg_list, - check_having_qual_for_aggs(lfirst(t), - subplanTargetList, groupClause)); - } - else - { - elog(ERROR, "You must group by the attribute used from outside!"); - return NIL; - } - } - return agg_list; - } + foreach(l1,groupClause) + { + if (tlist_member(lfirst(t),lcons(((GroupClause *)lfirst(l1))->entry,NIL)) != + NULL) + { + contained_in_group_clause=1; + } + } + + /* If the use of the attribute is allowed (i.e. it is in the groupClause) + * we have to adjust the varnos and varattnos */ + if (contained_in_group_clause) + { + agg_list = + nconc(agg_list, + check_having_qual_for_aggs(lfirst(t), + subplanTargetList, groupClause)); + } + else + { + elog(ERROR,"You must group by the attribute used from outside!"); + return NIL; + } + } + return agg_list; + } else - { - /* - * Ooops! we can not handle that! - */ - elog(ERROR, "check_having_qual_for_aggs: Can not handle this having_qual! %d\n", - nodeTag(clause)); - return NIL; - } + { + /* + * Ooops! we can not handle that! + */ + elog(ERROR, "check_having_qual_for_aggs: Can not handle this having_qual! %d\n", + nodeTag(clause)); + return NIL; + } } +/***S*H***/ /* End */ diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index a1b4cd22cc..b16203d9f7 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -405,20 +405,6 @@ SS_process_sublinks(Node *expr) SS_process_sublinks((Node *) ((Expr *) expr)->args); else if (IsA(expr, SubLink))/* got it! */ { - - /* - * Hack to make sure expr->oper->args points to the same VAR node - * as expr->lefthand does. Needed for subselects in the havingQual - * when used on views. Otherwise aggregate functions will fail - * later on (at execution time!) Reason: The rewite System makes - * several copies of the VAR nodes and in this case it should not - * do so :-( - */ - if (((SubLink *) expr)->lefthand != NULL) - { - lfirst(((Expr *) lfirst(((SubLink *) expr)->oper))->args) = - lfirst(((SubLink *) expr)->lefthand); - } expr = _make_subplan((SubLink *) expr); } diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 3dc0cad816..ea9cb5fa8c 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -5,7 +5,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: analyze.c,v 1.91 1998/12/14 06:50:32 scrappy Exp $ + * $Id: analyze.c,v 1.92 1999/01/18 00:09:49 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -26,6 +26,11 @@ #include "parser/parse_node.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" +/***S*I***/ +#include "parser/parse_expr.h" +#include "catalog/pg_type.h" +#include "parse.h" + #include "utils/builtins.h" #include "utils/mcxt.h" @@ -383,8 +388,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) * The INSERT INTO ... SELECT ... could have a UNION in child, so * unionClause may be false */ - qry->unionall = stmt->unionall; - qry->unionClause = transformUnionClause(stmt->unionClause, qry->targetList); + qry->unionall = stmt->unionall; + + /***S*I***/ + /* Just hand through the unionClause and intersectClause. + * We will handle it in the function Except_Intersect_Rewrite() */ + qry->unionClause = stmt->unionClause; + qry->intersectClause = stmt->intersectClause; /* * If there is a havingQual but there are no aggregates, then there is @@ -942,7 +952,12 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) * unionClause may be false */ qry->unionall = stmt->unionall; - qry->unionClause = transformUnionClause(stmt->unionClause, qry->targetList); + + /***S*I***/ + /* Just hand through the unionClause and intersectClause. + * We will handle it in the function Except_Intersect_Rewrite() */ + qry->unionClause = stmt->unionClause; + qry->intersectClause = stmt->intersectClause; /* * If there is a havingQual but there are no aggregates, then there is @@ -1012,3 +1027,97 @@ transformCursorStmt(ParseState *pstate, SelectStmt *stmt) return qry; } + +/***S*I***/ +/* This function steps through the tree + * built up by the select_w_o_sort rule + * and builds a list of all SelectStmt Nodes found + * The built up list is handed back in **select_list. + * If one of the SelectStmt Nodes has the 'unionall' flag + * set to true *unionall_present hands back 'true' */ +void +create_select_list(Node *ptr, List **select_list, bool *unionall_present) +{ + if(IsA(ptr, SelectStmt)) { + *select_list = lappend(*select_list, ptr); + if(((SelectStmt *)ptr)->unionall == TRUE) *unionall_present = TRUE; + return; + } + + /* Recursively call for all arguments. A NOT expr has no lexpr! */ + if (((A_Expr *)ptr)->lexpr != NULL) + create_select_list(((A_Expr *)ptr)->lexpr, select_list, unionall_present); + create_select_list(((A_Expr *)ptr)->rexpr, select_list, unionall_present); +} + +/* Changes the A_Expr Nodes to Expr Nodes and exchanges ANDs and ORs. + * The reason for the exchange is easy: We implement INTERSECTs and EXCEPTs + * by rewriting these queries to semantically equivalent queries that use + * IN and NOT IN subselects. To be able to use all three operations + * (UNIONs INTERSECTs and EXCEPTs) in one complex query we have to + * translate the queries into Disjunctive Normal Form (DNF). Unfortunately + * there is no function 'dnfify' but there is a function 'cnfify' + * which produces DNF when we exchange ANDs and ORs before calling + * 'cnfify' and exchange them back in the result. + * + * If an EXCEPT or INTERSECT is present *intersect_present + * hands back 'true' */ +Node *A_Expr_to_Expr(Node *ptr, bool *intersect_present) +{ + Node *result; + + switch(nodeTag(ptr)) + { + case T_A_Expr: + { + A_Expr *a = (A_Expr *)ptr; + + switch (a->oper) + { + case AND: + { + Expr *expr = makeNode(Expr); + Node *lexpr = A_Expr_to_Expr(((A_Expr *)ptr)->lexpr, intersect_present); + Node *rexpr = A_Expr_to_Expr(((A_Expr *)ptr)->rexpr, intersect_present); + + *intersect_present = TRUE; + + expr->typeOid = BOOLOID; + expr->opType = OR_EXPR; + expr->args = makeList(lexpr, rexpr, -1); + result = (Node *) expr; + break; + } + case OR: + { + Expr *expr = makeNode(Expr); + Node *lexpr = A_Expr_to_Expr(((A_Expr *)ptr)->lexpr, intersect_present); + Node *rexpr = A_Expr_to_Expr(((A_Expr *)ptr)->rexpr, intersect_present); + + expr->typeOid = BOOLOID; + expr->opType = AND_EXPR; + expr->args = makeList(lexpr, rexpr, -1); + result = (Node *) expr; + break; + } + case NOT: + { + Expr *expr = makeNode(Expr); + Node *rexpr = A_Expr_to_Expr(((A_Expr *)ptr)->rexpr, intersect_present); + + expr->typeOid = BOOLOID; + expr->opType = NOT_EXPR; + expr->args = makeList(rexpr, -1); + result = (Node *) expr; + break; + } + } + break; + } + default: + { + result = ptr; + } + } + return result; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 76e9dc1d7f..b3fb314a8f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.42 1999/01/05 15:46:25 vadim Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.43 1999/01/18 00:09:51 momjian Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -47,6 +47,7 @@ #include "access/xact.h" #include "storage/lmgr.h" #include "utils/numeric.h" +#include "parser/analyze.h" #ifdef MULTIBYTE #include "mb/pg_wchar.h" @@ -128,9 +129,9 @@ Oid param_type(int t); /* used in parse_expr.c */ ProcedureStmt, RecipeStmt, RemoveAggrStmt, RemoveOperStmt, RemoveFuncStmt, RemoveStmt, RenameStmt, RevokeStmt, RuleStmt, TransactionStmt, ViewStmt, LoadStmt, - CreatedbStmt, DestroydbStmt, VacuumStmt, CursorStmt, SubSelect, SubUnion, - UpdateStmt, InsertStmt, SelectStmt, NotifyStmt, DeleteStmt, ClusterStmt, - ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt, + CreatedbStmt, DestroydbStmt, VacuumStmt, CursorStmt, SubSelect, + UpdateStmt, InsertStmt, select_w_o_sort, SelectStmt, NotifyStmt, DeleteStmt, + ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt, CreateUserStmt, AlterUserStmt, DropUserStmt %type opt_database1, opt_database2, location, encoding @@ -174,7 +175,7 @@ Oid param_type(int t); /* used in parse_expr.c */ %type TriggerForOpt, TriggerForType -%type union_clause, select_list, for_update_clause +%type for_update_clause %type join_list %type join_using @@ -271,11 +272,11 @@ Oid param_type(int t); /* used in parse_expr.c */ CONSTRAINT, CREATE, CROSS, CURRENT, CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, CURSOR, DAY_P, DECIMAL, DECLARE, DEFAULT, DELETE, DESC, DISTINCT, DOUBLE, DROP, - ELSE, END_TRANS, EXECUTE, EXISTS, EXTRACT, + ELSE, END_TRANS, EXCEPT, EXECUTE, EXISTS, EXTRACT, FALSE_P, FETCH, FLOAT, FOR, FOREIGN, FROM, FULL, GRANT, GROUP, HAVING, HOUR_P, - IN, INNER_P, INSENSITIVE, INSERT, INTERVAL, INTO, IS, ISOLATION, - JOIN, KEY, LANGUAGE, LEADING, LEFT, LEVEL, LIKE, LOCAL, + IN, INNER_P, INSENSITIVE, INSERT, INTERSECT, INTERVAL, INTO, IS, + ISOLATION, JOIN, KEY, LANGUAGE, LEADING, LEFT, LEVEL, LIKE, LOCAL, MATCH, MINUTE_P, MONTH_P, NAMES, NATIONAL, NATURAL, NCHAR, NEXT, NO, NOT, NULLIF, NULL_P, NUMERIC, OF, ON, ONLY, OPTION, OR, ORDER, OUTER_P, @@ -343,7 +344,7 @@ Oid param_type(int t); /* used in parse_expr.c */ %left '.' %left '[' ']' %nonassoc TYPECAST -%left UNION +%left UNION INTERSECT EXCEPT %% stmtblock: stmtmulti @@ -354,8 +355,13 @@ stmtblock: stmtmulti stmtmulti: stmtmulti stmt ';' { $$ = lappend($1, $2); } - | stmtmulti stmt - { $$ = lappend($1, $2); } +/***S*I***/ +/* We comment the next rule because it seems to be redundant + * and produces 16 shift/reduce conflicts with the new SelectStmt rule + * needed for EXCEPT and INTERSECTS. So far I did not notice any + * violations by removing the rule! */ +/* | stmtmulti stmt + { $$ = lappend($1, $2); } */ | stmt ';' { $$ = lcons($1,NIL); } ; @@ -2062,7 +2068,10 @@ RuleStmt: CREATE RULE name AS OptStmtList: NOTHING { $$ = NIL; } | OptimizableStmt { $$ = lcons($1, NIL); } | '[' OptStmtBlock ']' { $$ = $2; } - | '(' OptStmtBlock ')' { $$ = $2; } +/***S*I*D***/ +/* We comment this out because it produces a shift / reduce conflict + * with the select_w_o_sort rule */ +/* | '(' OptStmtBlock ')' { $$ = $2; } */ ; OptStmtBlock: OptStmtMulti @@ -2073,8 +2082,13 @@ OptStmtBlock: OptStmtMulti OptStmtMulti: OptStmtMulti OptimizableStmt ';' { $$ = lappend($1, $2); } - | OptStmtMulti OptimizableStmt - { $$ = lappend($1, $2); } +/***S*I***/ +/* We comment the next rule because it seems to be redundant + * and produces 16 shift/reduce conflicts with the new SelectStmt rule + * needed for EXCEPT and INTERSECT. So far I did not notice any + * violations by removing the rule! */ +/* | OptStmtMulti OptimizableStmt + { $$ = lappend($1, $2); } */ | OptimizableStmt ';' { $$ = lcons($1, NIL); } ; @@ -2426,17 +2440,23 @@ OptimizableStmt: SelectStmt * *****************************************************************************/ -InsertStmt: INSERT INTO relation_name opt_column_list insert_rest +/***S*I***/ +/* This rule used 'opt_column_list' between 'relation_name' and 'insert_rest' + * originally. When the second rule of 'insert_rest' was changed to use + * the new 'SelectStmt' rule (for INTERSECT and EXCEPT) it produced a shift/reduce + * conflict. So I just changed the rules 'InsertStmt' and 'insert_rest' to accept + * the same statements without any shift/reduce conflicts */ +InsertStmt: INSERT INTO relation_name insert_rest { - $5->relname = $3; - $5->cols = $4; - $$ = (Node *)$5; + $4->relname = $3; + $$ = (Node *)$4; } ; insert_rest: VALUES '(' res_target_list2 ')' { $$ = makeNode(InsertStmt); + $$->cols = NULL; $$->unique = NULL; $$->targetList = $3; $$->fromClause = NIL; @@ -2455,20 +2475,57 @@ insert_rest: VALUES '(' res_target_list2 ')' $$->groupClause = NIL; $$->havingClause = NULL; $$->unionClause = NIL; + /***S*I***/ + $$->intersectClause = NIL; } - | SELECT opt_unique res_target_list2 - from_clause where_clause - group_clause having_clause - union_clause + /***S*I***/ + /* We want the full power of SelectStatements including INTERSECT and EXCEPT + * for insertion */ + | SelectStmt + { + SelectStmt *n; + + n = (SelectStmt *)$1; + $$ = makeNode(InsertStmt); + $$->cols = NULL; + $$->unique = n->unique; + $$->targetList = n->targetList; + $$->fromClause = n->fromClause; + $$->whereClause = n->whereClause; + $$->groupClause = n->groupClause; + $$->havingClause = n->havingClause; + $$->unionClause = n->unionClause; + $$->intersectClause = n->intersectClause; + } + | '(' columnList ')' VALUES '(' res_target_list2 ')' { $$ = makeNode(InsertStmt); - $$->unique = $2; - $$->targetList = $3; - $$->fromClause = $4; - $$->whereClause = $5; - $$->groupClause = $6; - $$->havingClause = $7; - $$->unionClause = $8; + $$->cols = $2; + $$->unique = NULL; + $$->targetList = $6; + $$->fromClause = NIL; + $$->whereClause = NULL; + $$->groupClause = NIL; + $$->havingClause = NULL; + $$->unionClause = NIL; + /***S*I***/ + $$->intersectClause = NIL; + } + | '(' columnList ')' SelectStmt + { + SelectStmt *n; + + n = (SelectStmt *)$4; + $$ = makeNode(InsertStmt); + $$->cols = $2; + $$->unique = n->unique; + $$->targetList = n->targetList; + $$->fromClause = n->fromClause; + $$->whereClause = n->whereClause; + $$->groupClause = n->groupClause; + $$->havingClause = n->havingClause; + $$->unionClause = n->unionClause; + $$->intersectClause = n->intersectClause; } ; @@ -2610,18 +2667,15 @@ UpdateStmt: UPDATE relation_name * CURSOR STATEMENTS * *****************************************************************************/ -CursorStmt: DECLARE name opt_cursor CURSOR FOR - SELECT opt_unique res_target_list2 - from_clause where_clause - group_clause having_clause - union_clause sort_clause - cursor_clause - { - SelectStmt *n = makeNode(SelectStmt); - - /* from PORTAL name */ - /* - * 15 august 1991 -- since 3.0 postgres does locking +/***S*I***/ +CursorStmt: DECLARE name opt_cursor CURSOR FOR SelectStmt cursor_clause + { + SelectStmt *n; + + n= (SelectStmt *)$6; + /* from PORTAL name */ + /* + * 15 august 1991 -- since 3.0 postgres does locking * right, we discovered that portals were violating * locking protocol. portal locks cannot span xacts. * as a short-term fix, we installed the check here. @@ -2632,14 +2686,6 @@ CursorStmt: DECLARE name opt_cursor CURSOR FOR n->portalname = $2; n->binary = $3; - n->unique = $7; - n->targetList = $8; - n->fromClause = $9; - n->whereClause = $10; - n->groupClause = $11; - n->havingClause = $12; - n->unionClause = $13; - n->sortClause = $14; $$ = (Node *)n; } ; @@ -2675,88 +2721,164 @@ opt_of: OF columnList * SELECT STATEMENTS * *****************************************************************************/ +/***S*I***/ +/* The new 'SelectStmt' rule adapted for the optional use of INTERSECT EXCEPT and UNION + * accepts the use of '(' and ')' to select an order of set operations. + * + * The rule returns a SelectStmt Node having the set operations attached to + * unionClause and intersectClause (NIL if no set operations were present) */ +SelectStmt: select_w_o_sort sort_clause for_update_clause + { + /* There were no set operations, so just attach the sortClause */ + if IsA($1, SelectStmt) + { + SelectStmt *n = (SelectStmt *)$1; + n->sortClause = $2; + n->forUpdate = $3; + $$ = (Node *)n; + } + /* There were set operations: The root of the operator tree + * is delivered by $1 but we cannot hand back an A_Expr Node. + * So we search for the leftmost 'SelectStmt' in the operator + * tree $1 (which is the first Select Statement in the query + * typed in by the user or where ever it came from). + * + * Then we attach the whole operator tree to 'intersectClause', + * and a list of all 'SelectStmt' Nodes to 'unionClause' and + * hand back the leftmost 'SelectStmt' Node. (We do it this way + * because the following functions (e.g. parse_analyze etc.) + * excpect a SelectStmt node and not an operator tree! The whole + * tree attached to 'intersectClause' won't be touched by + * parse_analyze() etc. until the function + * Except_Intersect_Rewrite() (in rewriteHandler.c) which performs + * the necessary steps to be able create a plan!) */ + else + { + List *select_list = NIL; + SelectStmt *first_select; + Node *op = (Node *) $1; + bool intersect_present = FALSE, unionall_present = FALSE; -SelectStmt: SELECT opt_unique res_target_list2 + /* Take the operator tree as an argument and + * create a list of all SelectStmt Nodes found in the tree. + * + * If one of the SelectStmt Nodes has the 'unionall' flag + * set to true the 'unionall_present' flag is also set to + * true */ + create_select_list((Node *)op, &select_list, &unionall_present); + + /* Replace all the A_Expr Nodes in the operator tree by + * Expr Nodes. + * + * If an INTERSECT or an EXCEPT is present, the + * 'intersect_present' flag is set to true */ + op = A_Expr_to_Expr(op, &intersect_present); + + /* If both flags are set to true we have a UNION ALL + * statement mixed up with INTERSECT or EXCEPT + * which can not be handled at the moment */ + if (intersect_present && unionall_present) + { + elog(ERROR,"UNION ALL not allowed in mixed set operations!"); + } + + /* Get the leftmost SeletStmt Node (which automatically + * represents the first Select Statement of the query!) */ + first_select = (SelectStmt *)lfirst(select_list); + + /* Attach the list of all SeletStmt Nodes to unionClause */ + first_select->unionClause = select_list; + + /* Attach the whole operator tree to intersectClause */ + first_select->intersectClause = (List *) op; + + /* finally attach the sort clause */ + first_select->sortClause = $2; + first_select>forUpdate = $3; + $$ = (Node *)first_select; + } + if ((SelectStmt *)$$)->forUpdate != NULL) + { + SelectStmt *n = (SelectStmt *)$1; + + if (n->unionClause != NULL) + elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION clause"); + if (n->unique != NULL) + elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause"); + if (n->groupClause != NULL) + elog(ERROR, "SELECT FOR UPDATE is not allowed with GROUP BY clause"); + if (n->havingClause != NULL) + elog(ERROR, "SELECT FOR UPDATE is not allowed with HAVING clause"); + } + } + ; + +/***S*I***/ +/* This rule parses Select statements including UNION INTERSECT and EXCEPT. + * '(' and ')' can be used to specify the order of the operations + * (UNION EXCEPT INTERSECT). Without the use of '(' and ')' we want the + * operations to be left associative. + * + * The sort_clause is not handled here! + * + * The rule builds up an operator tree using A_Expr Nodes. AND Nodes represent + * INTERSECTs OR Nodes represent UNIONs and AND NOT nodes represent EXCEPTs. + * The SelectStatements to be connected are the left and right arguments to + * the A_Expr Nodes. + * If no set operations show up in the query the tree consists only of one + * SelectStmt Node */ +select_w_o_sort: '(' select_w_o_sort ')' + { + $$ = $2; + } + | SubSelect + { + $$ = $1; + } + | select_w_o_sort EXCEPT select_w_o_sort + { + $$ = (Node *)makeA_Expr(AND,NULL,$1, + makeA_Expr(NOT,NULL,NULL,$3)); + } + | select_w_o_sort UNION opt_union select_w_o_sort + { + if (IsA($4, SelectStmt)) + { + SelectStmt *n = (SelectStmt *)$4; + n->unionall = $3; + } + $$ = (Node *)makeA_Expr(OR,NULL,$1,$4); + } + | select_w_o_sort INTERSECT select_w_o_sort + { + $$ = (Node *)makeA_Expr(AND,NULL,$1,$3); + } + ; + +/***S*I***/ +SubSelect: SELECT opt_unique res_target_list2 result from_clause where_clause - group_clause having_clause - union_clause sort_clause for_update_clause - { - SelectStmt *n = makeNode(SelectStmt); - n->unique = $2; - n->targetList = $3; - n->into = $4; - n->fromClause = $5; - n->whereClause = $6; - n->groupClause = $7; - n->havingClause = $8; - n->unionClause = $9; - n->sortClause = $10; - n->forUpdate = $11; - if (n->forUpdate != NULL) - { - if (n->unionClause != NULL) - elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION clause"); - if (n->unique != NULL) - elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause"); - if (n->groupClause != NULL) - elog(ERROR, "SELECT FOR UPDATE is not allowed with GROUP BY clause"); - if (n->havingClause != NULL) - elog(ERROR, "SELECT FOR UPDATE is not allowed with HAVING clause"); - } - else - $$ = (Node *)n; - } - ; - -SubSelect: SELECT opt_unique res_target_list2 - from_clause where_clause - group_clause having_clause - union_clause - { - SelectStmt *n = makeNode(SelectStmt); - n->unique = $2; - n->targetList = $3; - n->fromClause = $4; - n->whereClause = $5; - n->groupClause = $6; - n->havingClause = $7; - n->unionClause = $8; - $$ = (Node *)n; - } - ; - -union_clause: UNION opt_union select_list - { - SelectStmt *n = (SelectStmt *)lfirst($3); - n->unionall = $2; - $$ = $3; - } - | /*EMPTY*/ - { $$ = NIL; } - ; - -select_list: select_list UNION opt_union SubUnion - { - SelectStmt *n = (SelectStmt *)$4; - n->unionall = $3; - $$ = lappend($1, $4); - } - | SubUnion - { $$ = lcons($1, NIL); } - ; - -SubUnion: SELECT opt_unique res_target_list2 - from_clause where_clause group_clause having_clause { SelectStmt *n = makeNode(SelectStmt); n->unique = $2; n->unionall = FALSE; n->targetList = $3; - n->fromClause = $4; - n->whereClause = $5; - n->groupClause = $6; - n->havingClause = $7; + /***S*I***/ + /* This is new: Subselects support the INTO clause + * which allows queries that are not part of the + * SQL92 standard and should not be formulated! + * We need it for INTERSECT and EXCEPT and I did not + * want to create a new rule 'SubSelect1' including the + * feature. If it makes troubles we will have to add + * a new rule and change this to prevent INTOs in + * Subselects again */ + n->into = $4; + + n->fromClause = $5; + n->whereClause = $6; + n->groupClause = $7; + n->havingClause = $8; $$ = (Node *)n; } ; diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index e6da4f20a0..b7a9d003df 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.50 1998/12/18 09:10:34 vadim Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.51 1999/01/18 00:09:53 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -93,6 +93,9 @@ static ScanKeyword ScanKeywords[] = { {"else", ELSE}, {"encoding", ENCODING}, {"end", END_TRANS}, + /***S*I***/ + {"except", EXCEPT}, + {"execute", EXECUTE}, {"exists", EXISTS}, {"explain", EXPLAIN}, @@ -120,6 +123,9 @@ static ScanKeyword ScanKeywords[] = { {"insensitive", INSENSITIVE}, {"insert", INSERT}, {"instead", INSTEAD}, + /***S*I***/ + {"intersect", INTERSECT}, + {"interval", INTERVAL}, {"into", INTO}, {"is", IS}, diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index a8997d5a8a..b931bc744f 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -6,7 +6,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.27 1998/12/14 00:02:16 thomas Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.28 1999/01/18 00:09:54 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -24,6 +24,13 @@ #include "parser/parse_relation.h" #include "nodes/parsenodes.h" +/***S*I***/ +#include "parser/parse_node.h" +#include "parser/parse_target.h" + +#include "parser/analyze.h" +#include "optimizer/prep.h" + #include "rewrite/rewriteSupport.h" #include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" @@ -1661,7 +1668,8 @@ apply_RIR_view(Node **nodePtr, int rt_index, RangeTblEntry *rte, List *tlist, in case T_SubLink: { - SubLink *sub = (SubLink *)node; + SubLink *sub = (SubLink *)node; + List *tmp_lefthand, *tmp_oper; apply_RIR_view( (Node **)(&(sub->lefthand)), @@ -1678,6 +1686,15 @@ apply_RIR_view(Node **nodePtr, int rt_index, RangeTblEntry *rte, List *tlist, in tlist, modified, sublevels_up + 1); + + /***S*I***/ + tmp_lefthand = sub->lefthand; + foreach(tmp_oper, sub->oper) + { + lfirst(((Expr *) lfirst(tmp_oper))->args) = + lfirst(tmp_lefthand); + tmp_lefthand = lnext(tmp_lefthand); + } } break; @@ -2614,8 +2631,387 @@ QueryRewrite(Query *parsetree) query = (Query *)lfirst(l); results = lappend(results, fireRIRrules(query)); } - return results; } +/***S*I***/ +/* This function takes two targetlists as arguments and checks if the targetlists are compatible + * (i.e. both select for the same number of attributes and the types are compatible + */ +void check_targetlists_are_compatible(List *prev_target, List *current_target) +{ + List *next_target; + + if (length(prev_target) != + length(current_target)) + elog(ERROR,"Each UNION | EXCEPT | INTERSECT query must have the same number of columns."); + foreach(next_target, current_target) + { + Oid itype; + Oid otype; + + otype = ((TargetEntry *) lfirst(prev_target))->resdom->restype; + itype = ((TargetEntry *) lfirst(next_target))->resdom->restype; + + /* one or both is a NULL column? then don't convert... */ + if (otype == InvalidOid) + { + /* propagate a known type forward, if available */ + if (itype != InvalidOid) + ((TargetEntry *) lfirst(prev_target))->resdom->restype = itype; +#if FALSE + else + { + ((TargetEntry *) lfirst(prev_target))->resdom->restype = UNKNOWNOID; + ((TargetEntry *) lfirst(next_target))->resdom->restype = UNKNOWNOID; + } +#endif + } + else if (itype == InvalidOid) + { + } + /* they don't match in type? then convert... */ + else if (itype != otype) + { + Node *expr; + + expr = ((TargetEntry *) lfirst(next_target))->expr; + expr = CoerceTargetExpr(NULL, expr, itype, otype); + if (expr == NULL) + { + elog(ERROR, "Unable to transform %s to %s" + "\n\tEach UNION | EXCEPT | INTERSECT clause must have compatible target types", + typeidTypeName(itype), + typeidTypeName(otype)); + } + ((TargetEntry *) lfirst(next_target))->expr = expr; + ((TargetEntry *) lfirst(next_target))->resdom->restype = otype; + } + + /* both are UNKNOWN? then evaluate as text... */ + else if (itype == UNKNOWNOID) + { + ((TargetEntry *) lfirst(next_target))->resdom->restype = TEXTOID; + ((TargetEntry *) lfirst(prev_target))->resdom->restype = TEXTOID; + } + prev_target = lnext(prev_target); + } +} + +/***S*I***/ +/* Rewrites UNION INTERSECT and EXCEPT queries to semantiacally equivalent + * queries that use IN and NOT IN subselects. + * + * The operator tree is attached to 'intersectClause' (see rule + * 'SelectStmt' in gram.y) of the 'parsetree' given as an + * argument. First we remember some clauses (the sortClause, the + * unique flag etc.) Then we translate the operator tree to DNF + * (disjunctive normal form) by 'cnfify'. (Note that 'cnfify' produces + * CNF but as we exchanged ANDs with ORs in function A_Expr_to_Expr() + * earlier we get DNF after exchanging ANDs and ORs again in the + * result.) Now we create a new query by evaluating the new operator + * tree which is in DNF now. For every AND we create an entry in the + * union list and for every OR we create an IN subselect. (NOT IN + * subselects are created for OR NOT nodes). The first entry of the + * union list is handed back but before that the remembered clauses + * (sortClause etc) are attached to the new top Node (Note that the + * new top Node can differ from the parsetree given as argument because of + * the translation to DNF. That's why we have to remember the sortClause or + * unique flag!) */ +Query * +Except_Intersect_Rewrite (Query *parsetree) +{ + + SubLink *n; + Query *result, *intersect_node; + List *elist, *intersect_list = NIL, *intersect, *intersectClause; + List *union_list = NIL, *sortClause; + List *left_expr, *right_expr, *resnames = NIL; + char *op, *uniqueFlag, *into; + bool isBinary, isPortal; + CmdType commandType = CMD_SELECT; + List *rtable_insert = NIL; + + List *prev_target = NIL; + + /* Remember the Resnames of the given parsetree's targetlist + * (these are the resnames of the first Select Statement of + * the query formulated by the user and he wants the columns + * named by these strings. The transformation to DNF can + * cause another Select Statment to be the top one which + * uses other names for its columns. Therefore we remeber + * the original names and attach them to the targetlist + * of the new topmost Node at the end of this function */ + foreach(elist, parsetree->targetList) + { + TargetEntry *tent = (TargetEntry *)lfirst(elist); + + resnames = lappend(resnames, tent->resdom->resname); + } + + /* If the Statement is an INSERT INTO ... (SELECT...) statement + * using UNIONs, INTERSECTs or EXCEPTs and the transformation + * to DNF makes another Node to the top node we have to transform + * the new top node to an INSERT node and the original INSERT node + * to a SELECT node */ + if (parsetree->commandType == CMD_INSERT) + { + parsetree->commandType = CMD_SELECT; + commandType = CMD_INSERT; + parsetree->resultRelation = 0; + + /* The result relation ( = the one to insert into) has to be + * attached to the rtable list of the new top node */ + rtable_insert = nth(length(parsetree->rtable) - 1, parsetree->rtable); + } + + /* Save some items, to be able to attach them to the resulting top node + * at the end of the function */ + sortClause = parsetree->sortClause; + uniqueFlag = parsetree->uniqueFlag; + into = parsetree->into; + isBinary = parsetree->isBinary; + isPortal = parsetree->isPortal; + + /* The operator tree attached to parsetree->intersectClause is still 'raw' + * ( = the leaf nodes are still SelectStmt nodes instead of Query nodes) + * So step through the tree and transform the nodes using parse_analyze(). + * + * The parsetree (given as an argument to + * Except_Intersect_Rewrite()) has already been transformed and + * transforming it again would cause troubles. So we give the 'raw' + * version (of the cooked parsetree) to the function to + * prevent an additional transformation. Instead we hand back the + * 'cooked' version also given as an argument to + * intersect_tree_analyze() */ + intersectClause = + (List *)intersect_tree_analyze((Node *)parsetree->intersectClause, + (Node *)lfirst(parsetree->unionClause), + (Node *)parsetree); + + /* intersectClause is no longer needed so set it to NIL */ + parsetree->intersectClause = NIL; + /* unionClause will be needed later on but the list it delivered + * is no longer needed, so set it to NIL */ + parsetree->unionClause = NIL; + + /* Transform the operator tree to DNF (remember ANDs and ORs have been exchanged, + * that's why we get DNF by using cnfify) + * + * After the call, explicit ANDs are removed and all AND operands + * are simply items in the intersectClause list */ + intersectClause = cnfify((Expr *)intersectClause, true); + + /* For every entry of the intersectClause list we generate one entry in + * the union_list */ + foreach(intersect, intersectClause) + { + /* for every OR we create an IN subselect and for every OR NOT + * we create a NOT IN subselect, so first extract all the Select + * Query nodes from the tree (that contains only OR or OR NOTs + * any more because we did a transformation to DNF + * + * There must be at least one node that is not negated + * (i.e. just OR and not OR NOT) and this node will be the first + * in the list returned */ + intersect_list = NIL; + create_list((Node *)lfirst(intersect), &intersect_list); + + /* This one will become the Select Query node, all other + * nodes are transformed into subselects under this node! */ + intersect_node = (Query *)lfirst(intersect_list); + intersect_list = lnext(intersect_list); + + /* Check if all Select Statements use the same number of attributes and + * if all corresponding attributes are of the same type */ + if (prev_target) + check_targetlists_are_compatible(prev_target, intersect_node->targetList); + prev_target = intersect_node->targetList; + /* End of check for corresponding targetlists */ + + /* Transform all nodes remaining into subselects and add them to + * the qualifications of the Select Query node */ + while(intersect_list != NIL) { + + n = makeNode(SubLink); + + /* Here we got an OR so transform it to an IN subselect */ + if(IsA(lfirst(intersect_list), Query)) + { + /* Check if all Select Statements use the same number of attributes and + * if all corresponding attributes are of the same type */ + check_targetlists_are_compatible(prev_target, + ((Query *)lfirst(intersect_list))->targetList); + /* End of check for corresponding targetlists */ + + n->subselect = lfirst(intersect_list); + op = "="; + n->subLinkType = ANY_SUBLINK; + n->useor = false; + } + /* Here we got an OR NOT node so transform it to a NOT IN subselect */ + else + { + /* Check if all Select Statements use the same number of attributes and + * if all corresponding attributes are of the same type */ + check_targetlists_are_compatible(prev_target, + ((Query *)lfirst(((Expr *)lfirst(intersect_list))->args))->targetList); + /* End of check for corresponding targetlists */ + + n->subselect = (Node *)lfirst(((Expr *)lfirst(intersect_list))->args); + op = "<>"; + n->subLinkType = ALL_SUBLINK; + n->useor = true; + } + + /* Prepare the lefthand side of the Sublinks: All the entries of the + * targetlist must be (IN) or must not be (NOT IN) the subselect */ + foreach(elist, intersect_node->targetList) + { + Node *expr = lfirst(elist); + TargetEntry *tent = (TargetEntry *)expr; + + n->lefthand = lappend(n->lefthand, tent->expr); + } + + /* The first arguments of oper also have to be created for the + * sublink (they are the same as the lefthand side!) */ + left_expr = n->lefthand; + right_expr = ((Query *)(n->subselect))->targetList; + + foreach(elist, left_expr) + { + Node *lexpr = lfirst(elist); + Node *rexpr = lfirst(right_expr); + TargetEntry *tent = (TargetEntry *) rexpr; + Expr *op_expr; + + op_expr = make_op(op, lexpr, tent->expr); + + n->oper = lappend(n->oper, op_expr); + right_expr = lnext(right_expr); + } + + /* If the Select Query node has aggregates in use + * add all the subselects to the HAVING qual else to + * the WHERE qual */ + if(intersect_node->hasAggs == false) { + AddQual(intersect_node, (Node *)n); + } + else { + AddHavingQual(intersect_node, (Node *)n); + } + + /* Now we got sublinks */ + intersect_node->hasSubLinks = true; + intersect_list = lnext(intersect_list); + } + intersect_node->intersectClause = NIL; + union_list = lappend(union_list, intersect_node); + } + + /* The first entry to union_list is our new top node */ + result = (Query *)lfirst(union_list); + /* attach the rest to unionClause */ + result->unionClause = lnext(union_list); + /* Attach all the items remembered in the beginning of the function */ + result->sortClause = sortClause; + result->uniqueFlag = uniqueFlag; + result->into = into; + result->isPortal = isPortal; + result->isBinary = isBinary; + /* The relation to insert into is attached to the range table + * of the new top node */ + if (commandType == CMD_INSERT) + { + result->rtable = lappend(result->rtable, rtable_insert); + result->resultRelation = length(result->rtable); + result->commandType = commandType; + } + /* The resnames of the originally first SelectStatement are + * attached to the new first SelectStatement */ + foreach(elist, result->targetList) + { + TargetEntry *tent = (TargetEntry *)lfirst(elist); + + tent->resdom->resname = lfirst(resnames); + resnames = lnext(resnames); + } + return result; +} + +/* Create a list of nodes that are either Query nodes of NOT Expr + * nodes followed by a Query node. The tree given in ptr contains at + * least one non negated Query node. This node is attached to the + * beginning of the list */ + +void create_list(Node *ptr, List **intersect_list) +{ + List *arg; + + if(IsA(ptr,Query)) + { + /* The non negated node is attached at the beginning (lcons) */ + *intersect_list = lcons(ptr, *intersect_list); + return; + } + + if(IsA(ptr,Expr)) + { + if(((Expr *)ptr)->opType == NOT_EXPR) + { + /* negated nodes are appended to the end (lappend) */ + *intersect_list = lappend(*intersect_list, ptr); + return; + } + else + { + foreach(arg, ((Expr *)ptr)->args) + { + create_list(lfirst(arg), intersect_list); + } + return; + } + return; + } +} + +/* The nodes given in 'tree' are still 'raw' so 'cook' them using parse_analyze(). + * The node given in first_select has already been cooked, so don't transform + * it again but return a pointer to the previously cooked version given in 'parsetree' + * instead. */ +Node *intersect_tree_analyze(Node *tree, Node *first_select, Node *parsetree) +{ + Node *result = (Node *)NIL; + List *arg; + + if(IsA(tree, SelectStmt)) + { + QueryTreeList *qtree; + + /* If we get to the tree given in first_select return + * parsetree instead of performing parse_analyze() */ + if(tree == first_select){ + result = parsetree; + } + else { + /* transform the 'raw' nodes to 'cooked' Query nodes */ + qtree = parse_analyze(lcons(tree, NIL), NULL); + result = (Node *)qtree->qtrees[0]; + } + + } + if(IsA(tree,Expr)) + { + /* Call recursively for every argument of the node */ + foreach(arg, ((Expr *)tree)->args) + { + lfirst(arg) = intersect_tree_analyze(lfirst(arg), first_select, parsetree); + } + result = tree; + } + return result; +} + + diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 9d6d5e7d64..c958c7810f 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -6,7 +6,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.23 1998/12/14 00:02:17 thomas Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.24 1999/01/18 00:09:56 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -152,7 +152,10 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up) case T_SubLink: { SubLink *sub = (SubLink *)node; + List *tmp_oper, *tmp_lefthand; + /* We also have to adapt the variables used in sub->lefthand + * and sub->oper */ OffsetVarNodes( (Node *)(sub->lefthand), offset, @@ -162,6 +165,19 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up) (Node *)(sub->subselect), offset, sublevels_up + 1); + + /***S*I***/ + /* Make sure the first argument of sub->oper points to the + * same var as sub->lefthand does otherwise we will + * run into troubles using aggregates (aggno will not be + * set correctly) */ + tmp_lefthand = sub->lefthand; + foreach(tmp_oper, sub->oper) + { + lfirst(((Expr *) lfirst(tmp_oper))->args) = + lfirst(tmp_lefthand); + tmp_lefthand = lnext(tmp_lefthand); + } } break; @@ -364,7 +380,8 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up) case T_SubLink: { SubLink *sub = (SubLink *)node; - + List *tmp_oper, *tmp_lefthand; + ChangeVarNodes( (Node *)(sub->lefthand), rt_index, @@ -376,6 +393,19 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up) rt_index, new_index, sublevels_up + 1); + + /***S*I***/ + /* Make sure the first argument of sub->oper points to the + * same var as sub->lefthand does otherwise we will + * run into troubles using aggregates (aggno will not be + * set correctly) */ + tmp_lefthand = sub->lefthand; + foreach(tmp_oper, sub->oper) + { + lfirst(((Expr *) lfirst(tmp_oper))->args) = + lfirst(tmp_lefthand); + tmp_lefthand = lnext(tmp_lefthand); + } } break; @@ -465,7 +495,10 @@ AddQual(Query *parsetree, Node *qual) if (qual == NULL) return; - copy = copyObject(qual); + /***S*I***/ + /* copy = copyObject(qual); */ + copy = qual; + old = parsetree->qual; if (old == NULL) parsetree->qual = copy; @@ -485,7 +518,10 @@ AddHavingQual(Query *parsetree, Node *havingQual) if (havingQual == NULL) return; - copy = copyObject(havingQual); + /***S*I***/ + copy = havingQual; + /* copy = copyObject(havingQual); */ + old = parsetree->havingQual; if (old == NULL) parsetree->havingQual = copy; @@ -494,6 +530,20 @@ AddHavingQual(Query *parsetree, Node *havingQual) (Node *) make_andclause(makeList(parsetree->havingQual, copy, -1)); } +void +AddNotHavingQual(Query *parsetree, Node *havingQual) +{ + Node *copy; + + if (havingQual == NULL) + return; + + /***S*I***/ + /* copy = (Node *)make_notclause( (Expr *)copyObject(havingQual)); */ + copy = (Node *) make_notclause((Expr *)havingQual); + + AddHavingQual(parsetree, copy); +} void AddNotQual(Query *parsetree, Node *qual) @@ -503,7 +553,9 @@ AddNotQual(Query *parsetree, Node *qual) if (qual == NULL) return; - copy = (Node *) make_notclause(copyObject(qual)); + /***S*I***/ + /* copy = (Node *) make_notclause((Expr *)copyObject(qual)); */ + copy = (Node *) make_notclause((Expr *)qual); AddQual(parsetree, copy); } @@ -835,7 +887,7 @@ HandleRIRAttributeRule(Query *parsetree, rt_index, attr_num, modified, badsql, 0); } - +#ifdef NOT_USED static void nodeHandleViewRule(Node **nodePtr, List *rtable, @@ -976,10 +1028,19 @@ nodeHandleViewRule(Node **nodePtr, { SubLink *sublink = (SubLink *) node; Query *query = (Query *) sublink->subselect; + List *tmp_lefthand, *tmp_oper; + nodeHandleViewRule((Node **) &(query->qual), rtable, targetlist, rt_index, modified, sublevels_up + 1); + /***S*H*D***/ + nodeHandleViewRule((Node **) &(query->havingQual), rtable, targetlist, + rt_index, modified, sublevels_up + 1); + nodeHandleViewRule((Node **) &(query->targetList), rtable, targetlist, + rt_index, modified, sublevels_up + 1); + + /* * We also have to adapt the variables used in * sublink->lefthand and sublink->oper @@ -993,10 +1054,17 @@ nodeHandleViewRule(Node **nodePtr, * will run into troubles using aggregates (aggno will not * be set correctly */ - pfree(lfirst(((Expr *) lfirst(sublink->oper))->args)); - lfirst(((Expr *) lfirst(sublink->oper))->args) = - lfirst(sublink->lefthand); - } + /* pfree(lfirst(((Expr *) lfirst(sublink->oper))->args)); */ + + /***S*I***/ + tmp_lefthand = sublink->lefthand; + foreach(tmp_oper, sublink->oper) + { + lfirst(((Expr *) lfirst(tmp_oper))->args) = + lfirst(tmp_lefthand); + tmp_lefthand = lnext(tmp_lefthand); + } + } break; default: /* ignore the others */ @@ -1004,7 +1072,6 @@ nodeHandleViewRule(Node **nodePtr, } } -#ifdef NOT_USED void HandleViewRule(Query *parsetree, List *rtable, diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index eeec7b0225..f50dfee7d6 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.96 1999/01/17 06:18:42 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.97 1999/01/18 00:09:56 momjian Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -448,12 +448,20 @@ pg_parse_and_plan(char *query_string, /* string to execute */ querytree = querytree_list->qtrees[i]; + /***S*I***/ + /* Rewrite Union, Intersect and Except Queries + * to normal Union Queries using IN and NOT IN subselects */ + if(querytree->intersectClause != NIL) + { + querytree = Except_Intersect_Rewrite(querytree); + } + if (DebugPrintQuery) { if (DebugPrintQuery > 3) - { - /* Print the query string as is if query debug level > 3 */ - TPRINTF(TRACE_QUERY, "query: %s", query_string); + { + /* Print the query string as is if query debug level > 3 */ + TPRINTF(TRACE_QUERY, "query: %s", query_string); } else { @@ -1527,7 +1535,7 @@ PostgresMain(int argc, char *argv[], int real_argc, char *real_argv[]) if (!IsUnderPostmaster) { puts("\nPOSTGRES backend interactive interface "); - puts("$Revision: 1.96 $ $Date: 1999/01/17 06:18:42 $\n"); + puts("$Revision: 1.97 $ $Date: 1999/01/18 00:09:56 $\n"); } /* ---------------- diff --git a/src/include/executor/nodeGroup.h b/src/include/executor/nodeGroup.h index 0c23aa02d3..e56995fecd 100644 --- a/src/include/executor/nodeGroup.h +++ b/src/include/executor/nodeGroup.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: nodeGroup.h,v 1.7 1998/09/01 04:35:56 momjian Exp $ + * $Id: nodeGroup.h,v 1.8 1999/01/18 00:10:02 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -22,5 +22,8 @@ extern bool ExecInitGroup(Group *node, EState *estate, Plan *parent); extern int ExecCountSlotsGroup(Group *node); extern void ExecEndGroup(Group *node); extern void ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent); +/***S*I***/ +extern void ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent); + #endif /* NODEGROUP_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 41730bf346..d71d8c6b1e 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.65 1999/01/05 15:45:49 vadim Exp $ + * $Id: parsenodes.h,v 1.66 1999/01/18 00:10:06 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -58,6 +58,9 @@ typedef struct Query * BY */ Node *havingQual; /* qualification of each group */ + /***S*I***/ + List *intersectClause; + List *unionClause; /* unions are linked under the previous * query */ Node *limitOffset; /* # of result tuples to skip */ @@ -605,7 +608,9 @@ typedef struct InsertStmt List *groupClause; /* group by clause */ Node *havingClause; /* having conditional-expression */ List *unionClause; /* union subselect parameters */ - bool unionall; /* union without unique sort */ + bool unionall; /* union without unique sort */ + /***S*I***/ + List *intersectClause; } InsertStmt; /* ---------------------- @@ -646,6 +651,10 @@ typedef struct SelectStmt Node *whereClause; /* qualifications */ List *groupClause; /* group by clause */ Node *havingClause; /* having conditional-expression */ + /***S*I***/ + List *intersectClause; + List *exceptClause; + List *unionClause; /* union subselect parameters */ List *sortClause; /* sort clause (a list of SortGroupBy's) */ char *portalname; /* the portal (cursor) to create */ diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index 0c4f838d13..4be7637d74 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -5,7 +5,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: analyze.h,v 1.4 1998/09/01 04:37:25 momjian Exp $ + * $Id: analyze.h,v 1.5 1999/01/18 00:10:11 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -15,5 +15,8 @@ #include extern QueryTreeList *parse_analyze(List *pl, ParseState *parentParseState); +/***S*I***/ +extern void create_select_list(Node *ptr, List **select_list, bool *unionall_present); +extern Node *A_Expr_to_Expr(Node *ptr, bool *intersect_present); #endif /* ANALYZE_H */ diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h index ecec766aec..0adf71baab 100644 --- a/src/include/rewrite/rewriteHandler.h +++ b/src/include/rewrite/rewriteHandler.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: rewriteHandler.h,v 1.6 1998/09/01 04:38:01 momjian Exp $ + * $Id: rewriteHandler.h,v 1.7 1999/01/18 00:10:12 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -34,5 +34,9 @@ typedef struct _rewrite_meta_knowledge RewriteInfo; extern List *QueryRewrite(Query *parsetree); - +/***S*I***/ +extern Query *Except_Intersect_Rewrite(Query *parsetree); +extern void create_list(Node *ptr, List **intersect_list); +extern Node *intersect_tree_analyze(Node *tree, Node *first_select, Node *parsetree); +extern void check_targetlists_are_compatible(List *prev_target, List *current_target); #endif /* REWRITEHANDLER_H */ diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h index e1b54829bb..dc3724515b 100644 --- a/src/include/rewrite/rewriteManip.h +++ b/src/include/rewrite/rewriteManip.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: rewriteManip.h,v 1.11 1998/10/21 16:21:29 momjian Exp $ + * $Id: rewriteManip.h,v 1.12 1999/01/18 00:10:16 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -25,6 +25,8 @@ void AddQual(Query *parsetree, Node *qual); void AddHavingQual(Query *parsetree, Node *havingQual); void AddNotQual(Query *parsetree, Node *qual); +void AddNotHavingQual(Query *parsetree, Node *havingQual); + void FixNew(RewriteInfo *info, Query *parsetree); void HandleRIRAttributeRule(Query *parsetree, List *rtable, List *targetlist, diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 7d74644c62..258ef01aa4 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: elog.h,v 1.8 1998/09/01 04:39:03 momjian Exp $ + * $Id: elog.h,v 1.9 1999/01/18 00:10:17 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -29,7 +29,12 @@ #define ABORTX 0x4000 /* abort process after logging */ #endif -#define ELOG_MAXLEN 4096 +/***S*I***/ +/* Increase this to be able to use postmaster -d 3 with complex + * view definitions (which are transformed to very, very large INSERT statements + * and if -d 3 is used the query string of these statements is printed using + * vsprintf which expects enough memory reserved! */ +#define ELOG_MAXLEN 12288 /* uncomment the following if you want your elog's to be timestamped */