From 989067bd2208398d803e784582e96811d652f574 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 7 Apr 2004 18:17:25 +0000
Subject: [PATCH] Extend set-operation planning to keep track of the sort
 ordering induced by the set operation, so that redundant sorts at higher
 levels can be avoided.  This was foreseen a good while back, but not done. 
 Per request from Karel Zak.

---
 src/backend/optimizer/plan/planner.c   | 27 +++++++-----
 src/backend/optimizer/prep/prepunion.c | 61 +++++++++++++++++++-------
 src/include/optimizer/prep.h           |  4 +-
 3 files changed, 63 insertions(+), 29 deletions(-)

diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index fea7e4fef13..c3cfe4be719 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.167 2004/02/13 22:26:30 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.168 2004/04/07 18:17:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -572,11 +572,23 @@ grouping_planner(Query *parse, double tuple_fraction)
 
 	if (parse->setOperations)
 	{
+		List   *set_sortclauses;
+
 		/*
 		 * Construct the plan for set operations.  The result will not
 		 * need any work except perhaps a top-level sort and/or LIMIT.
 		 */
-		result_plan = plan_set_operations(parse);
+		result_plan = plan_set_operations(parse,
+										  &set_sortclauses);
+
+		/*
+		 * Calculate pathkeys representing the sort order (if any) of the
+		 * set operation's result.  We have to do this before overwriting
+		 * the sort key information...
+		 */
+		current_pathkeys = make_pathkeys_for_sortclauses(set_sortclauses,
+													result_plan->targetlist);
+		current_pathkeys = canonicalize_pathkeys(parse, current_pathkeys);
 
 		/*
 		 * We should not need to call preprocess_targetlist, since we must
@@ -599,16 +611,7 @@ grouping_planner(Query *parse, double tuple_fraction)
 					 errmsg("SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT")));
 
 		/*
-		 * We set current_pathkeys NIL indicating we do not know sort
-		 * order.  This is correct when the top set operation is UNION
-		 * ALL, since the appended-together results are unsorted even if
-		 * the subplans were sorted.  For other set operations we could be
-		 * smarter --- room for future improvement!
-		 */
-		current_pathkeys = NIL;
-
-		/*
-		 * Calculate pathkeys that represent ordering requirements
+		 * Calculate pathkeys that represent result ordering requirements
 		 */
 		sort_pathkeys = make_pathkeys_for_sortclauses(parse->sortClause,
 													  tlist);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 9424fc71482..b703bf2b86e 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.108 2004/01/18 00:50:02 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.109 2004/04/07 18:17:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,11 +45,12 @@ typedef struct
 
 static Plan *recurse_set_operations(Node *setOp, Query *parse,
 					   List *colTypes, bool junkOK,
-					   int flag, List *refnames_tlist);
+					   int flag, List *refnames_tlist,
+					   List **sortClauses);
 static Plan *generate_union_plan(SetOperationStmt *op, Query *parse,
-					List *refnames_tlist);
+					List *refnames_tlist, List **sortClauses);
 static Plan *generate_nonunion_plan(SetOperationStmt *op, Query *parse,
-					   List *refnames_tlist);
+					   List *refnames_tlist, List **sortClauses);
 static List *recurse_union_children(Node *setOp, Query *parse,
 					   SetOperationStmt *top_union,
 					   List *refnames_tlist);
@@ -75,9 +76,12 @@ static List *adjust_inherited_tlist(List *tlist, Oid old_relid, Oid new_relid);
  * This routine only deals with the setOperations tree of the given query.
  * Any top-level ORDER BY requested in parse->sortClause will be added
  * when we return to grouping_planner.
+ *
+ * *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(Query *parse)
+plan_set_operations(Query *parse, List **sortClauses)
 {
 	SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations;
 	Node	   *node;
@@ -113,7 +117,8 @@ plan_set_operations(Query *parse)
 	 */
 	return recurse_set_operations((Node *) topop, parse,
 								  topop->colTypes, true, -1,
-								  leftmostQuery->targetList);
+								  leftmostQuery->targetList,
+								  sortClauses);
 }
 
 /*
@@ -124,11 +129,13 @@ plan_set_operations(Query *parse)
  * junkOK: if true, child resjunk columns may be left in the result
  * flag: if >= 0, add a resjunk output column indicating value of flag
  * refnames_tlist: targetlist to take column names from
+ * *sortClauses: receives list of SortClauses for result plan, if any
  */
 static Plan *
 recurse_set_operations(Node *setOp, Query *parse,
 					   List *colTypes, bool junkOK,
-					   int flag, List *refnames_tlist)
+					   int flag, List *refnames_tlist,
+					   List **sortClauses)
 {
 	if (IsA(setOp, RangeTblRef))
 	{
@@ -155,6 +162,13 @@ recurse_set_operations(Node *setOp, Query *parse,
 							  NIL,
 							  rtr->rtindex,
 							  subplan);
+
+		/*
+		 * We don't bother to determine the subquery's output ordering
+		 * since it won't be reflected in the set-op result anyhow.
+		 */
+		*sortClauses = NIL;
+
 		return plan;
 	}
 	else if (IsA(setOp, SetOperationStmt))
@@ -164,9 +178,11 @@ recurse_set_operations(Node *setOp, Query *parse,
 
 		/* UNIONs are much different from INTERSECT/EXCEPT */
 		if (op->op == SETOP_UNION)
-			plan = generate_union_plan(op, parse, refnames_tlist);
+			plan = generate_union_plan(op, parse, refnames_tlist,
+									   sortClauses);
 		else
-			plan = generate_nonunion_plan(op, parse, refnames_tlist);
+			plan = generate_nonunion_plan(op, parse, refnames_tlist,
+										  sortClauses);
 
 		/*
 		 * If necessary, add a Result node to project the caller-requested
@@ -206,7 +222,8 @@ recurse_set_operations(Node *setOp, Query *parse,
  */
 static Plan *
 generate_union_plan(SetOperationStmt *op, Query *parse,
-					List *refnames_tlist)
+					List *refnames_tlist,
+					List **sortClauses)
 {
 	List	   *planlist;
 	List	   *tlist;
@@ -249,7 +266,11 @@ generate_union_plan(SetOperationStmt *op, Query *parse,
 		sortList = addAllTargetsToSortList(NULL, NIL, tlist, false);
 		plan = (Plan *) make_sort_from_sortclauses(parse, sortList, plan);
 		plan = (Plan *) make_unique(plan, sortList);
+		*sortClauses = sortList;
 	}
+	else
+		*sortClauses = NIL;
+
 	return plan;
 }
 
@@ -258,23 +279,27 @@ generate_union_plan(SetOperationStmt *op, Query *parse,
  */
 static Plan *
 generate_nonunion_plan(SetOperationStmt *op, Query *parse,
-					   List *refnames_tlist)
+					   List *refnames_tlist,
+					   List **sortClauses)
 {
 	Plan	   *lplan,
 			   *rplan,
 			   *plan;
 	List	   *tlist,
 			   *sortList,
-			   *planlist;
+			   *planlist,
+			   *child_sortclauses;
 	SetOpCmd	cmd;
 
 	/* Recurse on children, ensuring their outputs are marked */
 	lplan = recurse_set_operations(op->larg, parse,
 								   op->colTypes, false, 0,
-								   refnames_tlist);
+								   refnames_tlist,
+								   &child_sortclauses);
 	rplan = recurse_set_operations(op->rarg, parse,
 								   op->colTypes, false, 1,
-								   refnames_tlist);
+								   refnames_tlist,
+								   &child_sortclauses);
 	planlist = makeList2(lplan, rplan);
 
 	/*
@@ -315,6 +340,9 @@ generate_nonunion_plan(SetOperationStmt *op, Query *parse,
 			break;
 	}
 	plan = (Plan *) make_setop(cmd, plan, sortList, length(op->colTypes) + 1);
+
+	*sortClauses = sortList;
+
 	return plan;
 }
 
@@ -329,6 +357,8 @@ recurse_union_children(Node *setOp, Query *parse,
 					   SetOperationStmt *top_union,
 					   List *refnames_tlist)
 {
+	List   *child_sortclauses;
+
 	if (IsA(setOp, SetOperationStmt))
 	{
 		SetOperationStmt *op = (SetOperationStmt *) setOp;
@@ -359,7 +389,8 @@ recurse_union_children(Node *setOp, Query *parse,
 	 */
 	return makeList1(recurse_set_operations(setOp, parse,
 											top_union->colTypes, false,
-											-1, refnames_tlist));
+											-1, refnames_tlist,
+											&child_sortclauses));
 }
 
 /*
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 6255c362307..7f38bb795a7 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.43 2003/12/28 21:57:37 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.44 2004/04/07 18:17:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,7 +48,7 @@ extern List *preprocess_targetlist(List *tlist, int command_type,
 /*
  * prototypes for prepunion.c
  */
-extern Plan *plan_set_operations(Query *parse);
+extern Plan *plan_set_operations(Query *parse, List **sortClauses);
 
 extern List *find_all_inheritors(Oid parentrel);
 
-- 
GitLab