diff options
Diffstat (limited to 'src/backend/rewrite/rewriteHandler.c')
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 402 |
1 files changed, 399 insertions, 3 deletions
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index a8997d5a8ab..b931bc744fe 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; +} + + |