/* * 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, PlannerInfo *root, double tuple_fraction, SetOperationStmt *top_union, List *refnames_tlist) { List *child_sortclauses; if (IsA(setOp, SetOperationStmt)) { SetOperationStmt *op = (SetOperationStmt *) setOp; if (op->op == top_union->op && (op->all == top_union->all || op->all) && equal(op->colTypes, top_union->colTypes)) { /* Same UNION, so fold children into parent's subplan list */ return list_concat(recurse_union_children(op->larg, root, tuple_fraction, top_union, refnames_tlist), recurse_union_children(op->rarg, root, tuple_fraction, top_union, refnames_tlist)); } } /* * Not same, so plan this child separately. * * Note we disallow any resjunk columns in child results. This is * necessary since the Append node that implements the union won't do any * projection, and upper levels will get confused if some of our output * tuples have junk and some don't. This case only arises when we have an * EXCEPT or INTERSECT as child, else there won't be resjunk anyway. */ return list_make1(recurse_set_operations(setOp, root, tuple_fraction, top_union->colTypes, false, -1, refnames_tlist, &child_sortclauses)); }
/* * plan_set_operations * * Plans the queries for a tree of set operations (UNION/INTERSECT/EXCEPT) * * This routine only deals with the setOperations tree of the given query. * Any top-level ORDER BY requested in root->parse->sortClause will be added * when we return to grouping_planner. * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as for grouping_planner(); in particular, * zero means "all the tuples will be fetched". Any LIMIT present at the * top level has already been factored into tuple_fraction. * * *sortClauses is an output argument: it is set to a list of SortClauses * representing the result ordering of the topmost set operation. */ Plan * plan_set_operations(PlannerInfo *root, double tuple_fraction, List **sortClauses) { Query *parse = root->parse; SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations; Node *node; Query *leftmostQuery; Assert(topop && IsA(topop, SetOperationStmt)); /* check for unsupported stuff */ Assert(parse->utilityStmt == NULL); Assert(parse->jointree->fromlist == NIL); Assert(parse->jointree->quals == NULL); Assert(parse->groupClause == NIL); Assert(parse->havingQual == NULL); Assert(parse->distinctClause == NIL); /* * 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); /* * 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, plus possibly resjunk working columns (we can rely * on upper-level nodes to deal with that). */ return recurse_set_operations((Node *) topop, root, tuple_fraction, topop->colTypes, true, -1, leftmostQuery->targetList, sortClauses); }
/* * 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; if (op->op == top_union->op && (op->all == top_union->all || op->all) && equalo(op->colTypes, top_union->colTypes)) { /* 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. * * Note we disallow any resjunk columns in child results. This is * necessary since the Append node that implements the union won't do * any projection, and upper levels will get confused if some of our * output tuples have junk and some don't. This case only arises when * we have an EXCEPT or INTERSECT as child, else there won't be * resjunk anyway. */ return makeList1(recurse_set_operations(setOp, parse, top_union->colTypes, false, -1, refnames_tlist)); }
/* * 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, *planlist; SetOpCmd cmd; /* Recurse on children, ensuring their outputs are marked */ lplan = recurse_set_operations(op->larg, parse, op->colTypes, false, 0, refnames_tlist); rplan = recurse_set_operations(op->rarg, parse, op->colTypes, false, 1, refnames_tlist); planlist = makeList2(lplan, rplan); /* * Generate tlist for Append plan node. * * 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. In fact, it has to be real enough that the * flag column is shown as a variable not a constant, else setrefs.c * will get confused. */ tlist = generate_append_tlist(op->colTypes, true, planlist, refnames_tlist); /* * Append the child results together. */ plan = (Plan *) make_append(planlist, false, tlist); /* * Sort the child results, then add a SetOp plan node to generate the * correct output. */ tlist = copyObject(tlist); sortList = addAllTargetsToSortList(NULL, NIL, tlist, false); plan = (Plan *) make_sort_from_sortclauses(parse, 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, "unrecognized set op: %d", (int) op->op); cmd = SETOPCMD_INTERSECT; /* keep compiler quiet */ break; } plan = (Plan *) make_setop(cmd, tlist, plan, sortList, length(op->colTypes) + 1); return plan; }