diff options
Diffstat (limited to 'src/backend/optimizer/prep/prepunion.c')
-rw-r--r-- | src/backend/optimizer/prep/prepunion.c | 543 |
1 files changed, 362 insertions, 181 deletions
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index d3df8863270..0c91631563d 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1,27 +1,32 @@ /*------------------------------------------------------------------------- * * prepunion.c - * Routines to plan inheritance, union, and version queries + * Routines to plan set-operation and inheritance queries. The filename + * is a leftover from a time when only UNIONs were handled. * * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.53 2000/09/29 18:21:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.54 2000/10/05 19:11:30 tgl Exp $ * *------------------------------------------------------------------------- */ -#include <sys/types.h> - #include "postgres.h" +#include <sys/types.h> + +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/tlist.h" #include "parser/parse_clause.h" +#include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" @@ -33,221 +38,398 @@ typedef struct Oid new_relid; } fix_parsetree_attnums_context; +static Plan *recurse_set_operations(Node *setOp, Query *parse, + List *colTypes, int flag, + List *refnames_tlist); +static Plan *generate_union_plan(SetOperationStmt *op, Query *parse, + List *refnames_tlist); +static Plan *generate_nonunion_plan(SetOperationStmt *op, Query *parse, + List *refnames_tlist); +static List *recurse_union_children(Node *setOp, Query *parse, + SetOperationStmt *top_union, + List *refnames_tlist); +static List *generate_setop_tlist(List *colTypes, int flag, + List *input_tlist, + List *refnames_tlist); +static bool tlist_same_datatypes(List *tlist, List *colTypes); static void fix_parsetree_attnums(Index rt_index, Oid old_relid, Oid new_relid, Query *parsetree); static bool fix_parsetree_attnums_walker(Node *node, fix_parsetree_attnums_context *context); static RangeTblEntry *new_rangetable_entry(Oid new_relid, RangeTblEntry *old_entry); -static Append *make_append(List *appendplans, List *unionrtables, - Index rt_index, - List *inheritrtable, List *tlist); +static Append *make_append(List *appendplans, Index rt_index, + List *inheritrtable, List *tlist); /* - * plan_union_queries + * plan_set_operations * - * Plans the queries for a given UNION. + * Plans the queries for a tree of set operations (UNION/INTERSECT/EXCEPT) * - * Returns an Append plan that combines the results of the unioned queries. - * Note that Append output is correct for UNION ALL, but caller still needs - * to take care of sort/unique processing if it's a plain UNION. We set or - * clear the Query's fields so that the right things will happen back in - * union_planner. (This control structure is an unholy mess...) + * This routine only deals with the setOperations tree of the given query. + * Any top-level ORDER BY requested in parse->sortClause will be added on + * back in union_planner. */ Plan * -plan_union_queries(Query *parse) +plan_set_operations(Query *parse) { - List *union_plans = NIL, - *ulist, - *union_all_queries, - *union_rts, - *last_union = NIL, - *hold_sortClause = parse->sortClause; - bool union_all_found = false, - union_found = false, - last_union_all_flag = false; - - /*------------------------------------------------------------------ - * - * Do we need to split up our unions because we have UNION and UNION - * ALL? - * - * We are checking for the case of: SELECT 1 UNION SELECT 2 UNION SELECT - * 3 UNION ALL SELECT 4 UNION ALL SELECT 5 - * - * where we have to do a DISTINCT on the output of the first three - * queries, then add the rest. If they have used UNION and UNION ALL, - * we grab all queries up to the last UNION query, make them their own - * UNION with the owner as the first query in the list. Then, we take - * the remaining queries, which is UNION ALL, and add them to the list - * of union queries. - * - * So the above query becomes: - * - * Append Node - * { - * Sort and Unique - * { - * Append Node - * { - * SELECT 1 This is really a sub-UNION. - * unionClause We run a DISTINCT on these. - * { - * SELECT 2 - * SELECT 3 - * } - * } - * } - * SELECT 4 - * SELECT 5 - * } - * - *--------------------------------------------------------------------- + SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations; + Node *node; + Query *leftmostQuery; + + Assert(topop && IsA(topop, SetOperationStmt)); + + /* + * Find the leftmost component Query. We need to use its column names + * for all generated tlists (else SELECT INTO won't work right). */ + node = topop->larg; + while (node && IsA(node, SetOperationStmt)) + node = ((SetOperationStmt *) node)->larg; + Assert(node && IsA(node, RangeTblRef)); + leftmostQuery = rt_fetch(((RangeTblRef *) node)->rtindex, + parse->rtable)->subquery; + Assert(leftmostQuery != NULL); - foreach(ulist, parse->unionClause) + /* + * Recurse on setOperations tree to generate plans for set ops. + * The final output plan should have just the column types shown + * as the output from the top-level node. + */ + return recurse_set_operations((Node *) topop, parse, + topop->colTypes, -1, + leftmostQuery->targetList); +} + +/* + * recurse_set_operations + * Recursively handle one step in a tree of set operations + * + * colTypes: integer list of type OIDs of expected output columns + * flag: if >= 0, add a resjunk output column indicating value of flag + * refnames_tlist: targetlist to take column names from + */ +static Plan * +recurse_set_operations(Node *setOp, Query *parse, + List *colTypes, int flag, + List *refnames_tlist) +{ + if (IsA(setOp, RangeTblRef)) { - Query *union_query = lfirst(ulist); + RangeTblRef *rtr = (RangeTblRef *) setOp; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, parse->rtable); + Query *subquery = rte->subquery; + Plan *subplan, + *plan; - if (union_query->unionall) - union_all_found = true; - else - { - union_found = true; - last_union = ulist; - } - last_union_all_flag = union_query->unionall; + Assert(subquery != NULL); + /* + * Generate plan for primitive subquery + */ + subplan = subquery_planner(subquery, + -1.0 /* default case */ ); + /* + * Add a SubqueryScan with the caller-requested targetlist + */ + plan = (Plan *) + make_subqueryscan(generate_setop_tlist(colTypes, flag, + subplan->targetlist, + refnames_tlist), + NIL, + rtr->rtindex, + subplan); + copy_plan_costsize(plan, subplan); + return plan; } - - /* Is this a simple one */ - if (!union_all_found || - !union_found || - /* A trailing UNION negates the effect of earlier UNION ALLs */ - !last_union_all_flag) + else if (IsA(setOp, SetOperationStmt)) { - List *hold_unionClause = parse->unionClause; - double tuple_fraction = -1.0; /* default processing */ - - /* we will do sorting later, so don't do it now */ - if (!union_all_found || - !last_union_all_flag) - { - parse->sortClause = NIL; - parse->distinctClause = NIL; - - /* - * force lower-level planning to assume that all tuples will - * be retrieved, even if it sees a LIMIT in the query node. - */ - tuple_fraction = 0.0; - } - - parse->unionClause = NIL; /* prevent recursion */ - union_plans = lcons(union_planner(parse, tuple_fraction), NIL); - union_rts = lcons(parse->rtable, NIL); + SetOperationStmt *op = (SetOperationStmt *) setOp; + Plan *plan; - foreach(ulist, hold_unionClause) + /* UNIONs are much different from INTERSECT/EXCEPT */ + if (op->op == SETOP_UNION) + plan = generate_union_plan(op, parse, refnames_tlist); + else + plan = generate_nonunion_plan(op, parse, refnames_tlist); + /* + * If necessary, add a Result node to project the caller-requested + * output columns. + * + * XXX you don't really want to know about this: setrefs.c will apply + * replace_vars_with_subplan_refs() to the Result node's tlist. + * This would fail if the input plan's non-resjunk tlist entries were + * not all simple Vars equal() to the referencing Vars generated by + * generate_setop_tlist(). However, since the input plan was + * generated by generate_union_plan() or generate_nonunion_plan(), + * the referencing Vars will equal the tlist entries they reference. + * Ugly but I don't feel like making that code more general right now. + */ + if (flag >= 0 || ! tlist_same_datatypes(plan->targetlist, colTypes)) { - Query *union_query = lfirst(ulist); - - /* - * use subquery_planner here because the union'd queries have - * not been preprocessed yet. My goodness this is messy... - */ - union_plans = lappend(union_plans, - subquery_planner(union_query, - tuple_fraction)); - union_rts = lappend(union_rts, union_query->rtable); + plan = (Plan *) + make_result(generate_setop_tlist(colTypes, flag, + plan->targetlist, + refnames_tlist), + NULL, + plan); } + return plan; } else { + elog(ERROR, "recurse_set_operations: unexpected node %d", + (int) nodeTag(setOp)); + return NULL; /* keep compiler quiet */ + } +} - /* - * We have mixed unions and non-unions - * - * We need to restructure this to put the UNIONs on their own so we - * can do a DISTINCT. - */ +/* + * Generate plan for a UNION or UNION ALL node + */ +static Plan * +generate_union_plan(SetOperationStmt *op, Query *parse, + List *refnames_tlist) +{ + List *planlist; + Plan *plan; + + /* + * If any of my children are identical UNION nodes (same op, all-flag, + * and colTypes) then they can be merged into this node so that we + * generate only one Append and Sort for the lot. Recurse to find + * such nodes and compute their children's plans. + */ + planlist = nconc(recurse_union_children(op->larg, parse, + op, refnames_tlist), + recurse_union_children(op->rarg, parse, + op, refnames_tlist)); + /* + * Append the child results together. + * + * The tlist for an Append plan isn't important as far as the Append + * is concerned, but we must make it look real anyway for the benefit + * of the next plan level up. + */ + plan = (Plan *) + make_append(planlist, + 0, + NIL, + generate_setop_tlist(op->colTypes, -1, + ((Plan *) lfirst(planlist))->targetlist, + refnames_tlist)); + /* + * For UNION ALL, we just need the Append plan. For UNION, + * need to add Sort and Unique nodes to produce unique output. + */ + if (! op->all) + { + List *tlist, + *sortList; - /* save off everthing past the last UNION */ - union_all_queries = lnext(last_union); + tlist = new_unsorted_tlist(plan->targetlist); + sortList = addAllTargetsToSortList(NIL, tlist); + plan = make_sortplan(tlist, plan, sortList); + plan = (Plan *) make_unique(tlist, plan, copyObject(sortList)); + } + return plan; +} - /* clip off the list to remove the trailing UNION ALLs */ - lnext(last_union) = NIL; +/* + * Generate plan for an INTERSECT, INTERSECT ALL, EXCEPT, or EXCEPT ALL node + */ +static Plan * +generate_nonunion_plan(SetOperationStmt *op, Query *parse, + List *refnames_tlist) +{ + Plan *lplan, + *rplan, + *plan; + List *tlist, + *sortList; + SetOpCmd cmd; + + /* Recurse on children, ensuring their outputs are marked */ + lplan = recurse_set_operations(op->larg, parse, + op->colTypes, 0, + refnames_tlist); + rplan = recurse_set_operations(op->rarg, parse, + op->colTypes, 1, + refnames_tlist); + /* + * Append the child results together. + * + * The tlist for an Append plan isn't important as far as the Append + * is concerned, but we must make it look real anyway for the benefit + * of the next plan level up. + */ + plan = (Plan *) + make_append(makeList2(lplan, rplan), + 0, + NIL, + generate_setop_tlist(op->colTypes, 0, + lplan->targetlist, + refnames_tlist)); + /* + * Sort the child results, then add a SetOp plan node to + * generate the correct output. + */ + tlist = new_unsorted_tlist(plan->targetlist); + sortList = addAllTargetsToSortList(NIL, tlist); + plan = make_sortplan(tlist, plan, sortList); + switch (op->op) + { + case SETOP_INTERSECT: + cmd = op->all ? SETOPCMD_INTERSECT_ALL : SETOPCMD_INTERSECT; + break; + case SETOP_EXCEPT: + cmd = op->all ? SETOPCMD_EXCEPT_ALL : SETOPCMD_EXCEPT; + break; + default: + elog(ERROR, "generate_nonunion_plan: bogus operation code"); + cmd = SETOPCMD_INTERSECT; /* keep compiler quiet */ + break; + } + plan = (Plan *) make_setop(cmd, tlist, plan, sortList, + length(op->colTypes)+1); + return plan; +} - /* - * Recursion, but UNION only. The last one is a UNION, so it will - * not come here in recursion. - * - * XXX is it OK to pass default -1 to union_planner in this path, or - * should we force a tuple_fraction value? - */ - union_plans = lcons(union_planner(parse, -1.0), NIL); - union_rts = lcons(parse->rtable, NIL); +/* + * Pull up children of a UNION node that are identically-propertied UNIONs. + * + * NOTE: we can also pull a UNION ALL up into a UNION, since the distinct + * output rows will be lost anyway. + */ +static List * +recurse_union_children(Node *setOp, Query *parse, + SetOperationStmt *top_union, + List *refnames_tlist) +{ + if (IsA(setOp, SetOperationStmt)) + { + SetOperationStmt *op = (SetOperationStmt *) setOp; - /* Append the remaining UNION ALLs */ - foreach(ulist, union_all_queries) + if (op->op == top_union->op && + (op->all == top_union->all || op->all) && + equali(op->colTypes, top_union->colTypes)) { - Query *union_all_query = lfirst(ulist); - - /* - * use subquery_planner here because the union'd queries have - * not been preprocessed yet. My goodness this is messy... - */ - union_plans = lappend(union_plans, - subquery_planner(union_all_query, -1.0)); - union_rts = lappend(union_rts, union_all_query->rtable); + /* Same UNION, so fold children into parent's subplan list */ + return nconc(recurse_union_children(op->larg, parse, + top_union, refnames_tlist), + recurse_union_children(op->rarg, parse, + top_union, refnames_tlist)); } } + /* Not same, so plan this child separately */ + return makeList1(recurse_set_operations(setOp, parse, + top_union->colTypes, -1, + refnames_tlist)); +} - /* We have already split UNION and UNION ALL and we made it consistent */ - if (!last_union_all_flag) - { +/* + * Generate targetlist for a set-operation plan node + */ +static List * +generate_setop_tlist(List *colTypes, int flag, + List *input_tlist, + List *refnames_tlist) +{ + List *tlist = NIL; + int resno = 1; + List *i; + Resdom *resdom; + Node *expr; + foreach(i, colTypes) + { + Oid colType = (Oid) lfirsti(i); + TargetEntry *inputtle = (TargetEntry *) lfirst(input_tlist); + TargetEntry *reftle = (TargetEntry *) lfirst(refnames_tlist); + + Assert(inputtle->resdom->resno == resno); + Assert(reftle->resdom->resno == resno); + Assert(!inputtle->resdom->resjunk); + Assert(!reftle->resdom->resjunk); /* - * Need SELECT DISTINCT behavior to implement UNION. Put back the - * held sortClause, add any missing columns to the sort clause, - * and set distinctClause properly. + * Generate columns referencing input columns and having + * appropriate data types and column names. Insert datatype + * coercions where necessary. + * + * HACK: constants in the input's targetlist are copied up as-is + * rather than being referenced as subquery outputs. This is mainly + * to ensure that when we try to coerce them to the output column's + * datatype, the right things happen for UNKNOWN constants. */ - List *slitem; - - parse->sortClause = addAllTargetsToSortList(hold_sortClause, - parse->targetList); - parse->distinctClause = NIL; - foreach(slitem, parse->sortClause) - { - SortClause *scl = (SortClause *) lfirst(slitem); - TargetEntry *tle = get_sortgroupclause_tle(scl, parse->targetList); - - if (!tle->resdom->resjunk) - parse->distinctClause = lappend(parse->distinctClause, - copyObject(scl)); - } + resdom = makeResdom((AttrNumber) resno++, + colType, + -1, + pstrdup(reftle->resdom->resname), + false); + if (inputtle->expr && IsA(inputtle->expr, Const)) + expr = inputtle->expr; + else + expr = (Node *) makeVar(0, + inputtle->resdom->resno, + inputtle->resdom->restype, + inputtle->resdom->restypmod, + 0); + expr = coerce_to_common_type(NULL, + expr, + colType, + "UNION/INTERSECT/EXCEPT"); + tlist = lappend(tlist, makeTargetEntry(resdom, expr)); + input_tlist = lnext(input_tlist); + refnames_tlist = lnext(refnames_tlist); } - else + + if (flag >= 0) { - /* needed so we don't take SELECT DISTINCT from the first query */ - parse->distinctClause = NIL; + /* Add a resjunk column yielding specified flag value */ + resdom = makeResdom((AttrNumber) resno++, + INT4OID, + -1, + pstrdup("flag"), + true); + expr = (Node *) makeConst(INT4OID, + sizeof(int4), + Int32GetDatum(flag), + false, + true, + false, + false); + tlist = lappend(tlist, makeTargetEntry(resdom, expr)); } - /* - * Make sure we don't try to apply the first query's grouping stuff to - * the Append node, either. Basically we don't want union_planner to - * do anything when we return control, except add the top sort/unique - * nodes for DISTINCT processing if this wasn't UNION ALL, or the top - * sort node if it was UNION ALL with a user-provided sort clause. - */ - parse->groupClause = NULL; - parse->havingQual = NULL; - parse->hasAggs = false; + return tlist; +} - return (Plan *) make_append(union_plans, - union_rts, - 0, - NIL, - parse->targetList); +/* + * Does tlist have same datatypes as requested colTypes? + * + * Resjunk columns are ignored. + */ +static bool +tlist_same_datatypes(List *tlist, List *colTypes) +{ + List *i; + + foreach(i, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(i); + + if (!tle->resdom->resjunk) + { + if (colTypes == NIL) + return false; + if (tle->resdom->restype != (Oid) lfirsti(colTypes)) + return false; + colTypes = lnext(colTypes); + } + } + if (colTypes != NIL) + return false; + return true; } @@ -372,7 +554,6 @@ plan_inherit_queries(Query *root, List *tlist, /* Construct the finished Append plan. */ return (Plan *) make_append(union_plans, - NIL, rt_index, union_rtentries, ((Plan *) lfirst(union_plans))->targetlist); @@ -530,7 +711,8 @@ fix_parsetree_attnums(Index rt_index, query_tree_walker(parsetree, fix_parsetree_attnums_walker, - (void *) &context); + (void *) &context, + true); } /* @@ -570,7 +752,8 @@ fix_parsetree_attnums_walker(Node *node, context->sublevels_up++; result = query_tree_walker((Query *) node, fix_parsetree_attnums_walker, - (void *) context); + (void *) context, + true); context->sublevels_up--; return result; } @@ -580,7 +763,6 @@ fix_parsetree_attnums_walker(Node *node, static Append * make_append(List *appendplans, - List *unionrtables, Index rt_index, List *inheritrtable, List *tlist) @@ -589,7 +771,6 @@ make_append(List *appendplans, List *subnode; node->appendplans = appendplans; - node->unionrtables = unionrtables; node->inheritrelid = rt_index; node->inheritrtable = inheritrtable; node->plan.startup_cost = 0; |