If a LIMIT is applied to a UNION ALL query, plan each UNION arm as
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Jun 2005 02:21:05 +0000 (02:21 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Jun 2005 02:21:05 +0000 (02:21 +0000)
if the limit were directly applied to it.  This does not actually
add a LIMIT plan node to the generated subqueries --- that would be
useless overhead --- but it does cause the planner to prefer fast-
start plans when the limit is small.  After an idea from Phil Endecott.

src/backend/optimizer/plan/planner.c
src/backend/optimizer/prep/prepunion.c
src/include/optimizer/prep.h

index 76ffe04078fabfe7014f2a9fc28d9aa3189c92f3..df8d0556b42cfdc9dbbce31abe716811d8664769 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.188 2005/06/05 22:32:56 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.189 2005/06/10 02:21:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -58,6 +58,8 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
 static Plan *inheritance_planner(PlannerInfo *root, List *inheritlist);
 static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction);
+static double adjust_tuple_fraction_for_limit(PlannerInfo *root,
+                                             double tuple_fraction);
 static bool choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
                       Path *cheapest_path, Path *sorted_path,
                       List *sort_pathkeys, List *group_pathkeys,
@@ -648,15 +650,30 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
    List       *current_pathkeys;
    List       *sort_pathkeys;
 
+   /* Tweak caller-supplied tuple_fraction if have LIMIT */
+   if (parse->limitCount != NULL)
+       tuple_fraction = adjust_tuple_fraction_for_limit(root, tuple_fraction);
+
    if (parse->setOperations)
    {
        List       *set_sortclauses;
 
+       /*
+        * If there's a top-level ORDER BY, assume we have to fetch all
+        * the tuples.  This might seem too simplistic given all the
+        * hackery below to possibly avoid the sort ... but a nonzero
+        * tuple_fraction is only of use to plan_set_operations() when
+        * the setop is UNION ALL, and the result of UNION ALL is always
+        * unsorted.
+        */
+       if (parse->sortClause)
+           tuple_fraction = 0.0;
+
        /*
         * 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(root,
+       result_plan = plan_set_operations(root, tuple_fraction,
                                          &set_sortclauses);
 
        /*
@@ -769,108 +786,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        else
            root->query_pathkeys = NIL;
 
-       /*
-        * Adjust tuple_fraction if we see that we are going to apply
-        * limiting/grouping/aggregation/etc.  This is not overridable by
-        * the caller, since it reflects plan actions that this routine
-        * will certainly take, not assumptions about context.
-        */
-       if (parse->limitCount != NULL)
-       {
-           /*
-            * A LIMIT clause limits the absolute number of tuples
-            * returned. However, if it's not a constant LIMIT then we
-            * have to punt; for lack of a better idea, assume 10% of the
-            * plan's result is wanted.
-            */
-           double      limit_fraction = 0.0;
-
-           if (IsA(parse->limitCount, Const))
-           {
-               Const      *limitc = (Const *) parse->limitCount;
-               int32       count = DatumGetInt32(limitc->constvalue);
-
-               /*
-                * A NULL-constant LIMIT represents "LIMIT ALL", which we
-                * treat the same as no limit (ie, expect to retrieve all
-                * the tuples).
-                */
-               if (!limitc->constisnull && count > 0)
-               {
-                   limit_fraction = (double) count;
-                   /* We must also consider the OFFSET, if present */
-                   if (parse->limitOffset != NULL)
-                   {
-                       if (IsA(parse->limitOffset, Const))
-                       {
-                           int32       offset;
-
-                           limitc = (Const *) parse->limitOffset;
-                           offset = DatumGetInt32(limitc->constvalue);
-                           if (!limitc->constisnull && offset > 0)
-                               limit_fraction += (double) offset;
-                       }
-                       else
-                       {
-                           /* OFFSET is an expression ... punt ... */
-                           limit_fraction = 0.10;
-                       }
-                   }
-               }
-           }
-           else
-           {
-               /* LIMIT is an expression ... punt ... */
-               limit_fraction = 0.10;
-           }
-
-           if (limit_fraction > 0.0)
-           {
-               /*
-                * If we have absolute limits from both caller and LIMIT,
-                * use the smaller value; if one is fractional and the
-                * other absolute, treat the fraction as a fraction of the
-                * absolute value; else we can multiply the two fractions
-                * together.
-                */
-               if (tuple_fraction >= 1.0)
-               {
-                   if (limit_fraction >= 1.0)
-                   {
-                       /* both absolute */
-                       tuple_fraction = Min(tuple_fraction, limit_fraction);
-                   }
-                   else
-                   {
-                       /* caller absolute, limit fractional */
-                       tuple_fraction *= limit_fraction;
-                       if (tuple_fraction < 1.0)
-                           tuple_fraction = 1.0;
-                   }
-               }
-               else if (tuple_fraction > 0.0)
-               {
-                   if (limit_fraction >= 1.0)
-                   {
-                       /* caller fractional, limit absolute */
-                       tuple_fraction *= limit_fraction;
-                       if (tuple_fraction < 1.0)
-                           tuple_fraction = 1.0;
-                   }
-                   else
-                   {
-                       /* both fractional */
-                       tuple_fraction *= limit_fraction;
-                   }
-               }
-               else
-               {
-                   /* no info from caller, just use limit */
-                   tuple_fraction = limit_fraction;
-               }
-           }
-       }
-
        /*
         * With grouping or aggregation, the tuple fraction to pass to
         * query_planner() may be different from what it is at top level.
@@ -1242,6 +1157,114 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
    return result_plan;
 }
 
+/*
+ * adjust_tuple_fraction_for_limit - adjust tuple fraction for LIMIT
+ *
+ * If the query contains LIMIT, we adjust the caller-supplied tuple_fraction
+ * accordingly.  This is not overridable by the caller, since it reflects plan
+ * actions that grouping_planner() will certainly take, not assumptions about
+ * context.
+ */
+static double
+adjust_tuple_fraction_for_limit(PlannerInfo *root, double tuple_fraction)
+{
+   Query      *parse = root->parse;
+   double      limit_fraction = 0.0;
+
+   /* Should not be called unless LIMIT */
+   Assert(parse->limitCount != NULL);
+
+   /*
+    * A LIMIT clause limits the absolute number of tuples returned. However,
+    * if it's not a constant LIMIT then we have to punt; for lack of a better
+    * idea, assume 10% of the plan's result is wanted.
+    */
+   if (IsA(parse->limitCount, Const))
+   {
+       Const      *limitc = (Const *) parse->limitCount;
+       int32       count = DatumGetInt32(limitc->constvalue);
+
+       /*
+        * A NULL-constant LIMIT represents "LIMIT ALL", which we treat the
+        * same as no limit (ie, expect to retrieve all the tuples).
+        */
+       if (!limitc->constisnull && count > 0)
+       {
+           limit_fraction = (double) count;
+           /* We must also consider the OFFSET, if present */
+           if (parse->limitOffset != NULL)
+           {
+               if (IsA(parse->limitOffset, Const))
+               {
+                   int32       offset;
+
+                   limitc = (Const *) parse->limitOffset;
+                   offset = DatumGetInt32(limitc->constvalue);
+                   if (!limitc->constisnull && offset > 0)
+                       limit_fraction += (double) offset;
+               }
+               else
+               {
+                   /* OFFSET is an expression ... punt ... */
+                   limit_fraction = 0.10;
+               }
+           }
+       }
+   }
+   else
+   {
+       /* LIMIT is an expression ... punt ... */
+       limit_fraction = 0.10;
+   }
+
+   if (limit_fraction > 0.0)
+   {
+       /*
+        * If we have absolute limits from both caller and LIMIT, use the
+        * smaller value; if one is fractional and the other absolute,
+        * treat the fraction as a fraction of the absolute value;
+        * else we can multiply the two fractions together.
+        */
+       if (tuple_fraction >= 1.0)
+       {
+           if (limit_fraction >= 1.0)
+           {
+               /* both absolute */
+               tuple_fraction = Min(tuple_fraction, limit_fraction);
+           }
+           else
+           {
+               /* caller absolute, limit fractional */
+               tuple_fraction *= limit_fraction;
+               if (tuple_fraction < 1.0)
+                   tuple_fraction = 1.0;
+           }
+       }
+       else if (tuple_fraction > 0.0)
+       {
+           if (limit_fraction >= 1.0)
+           {
+               /* caller fractional, limit absolute */
+               tuple_fraction *= limit_fraction;
+               if (tuple_fraction < 1.0)
+                   tuple_fraction = 1.0;
+           }
+           else
+           {
+               /* both fractional */
+               tuple_fraction *= limit_fraction;
+           }
+       }
+       else
+       {
+           /* no info from caller, just use limit */
+           tuple_fraction = limit_fraction;
+       }
+   }
+
+   return tuple_fraction;
+}
+
 /*
  * choose_hashed_grouping - should we use hashed grouping?
  */
index 2139ac23f1c1e3b0eb0a063465dbeffbefb68f54..1fedd9791cada261d3ef12a433f75ba29ffed60b 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.123 2005/06/09 04:18:59 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.124 2005/06/10 02:21:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,16 +50,19 @@ typedef struct
 } adjust_inherited_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
-                      List *colTypes, bool junkOK,
-                      int flag, List *refnames_tlist,
-                      List **sortClauses);
+                                   double tuple_fraction,
+                                   List *colTypes, bool junkOK,
+                                   int flag, List *refnames_tlist,
+                                   List **sortClauses);
 static Plan *generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
-                   List *refnames_tlist, List **sortClauses);
+                                double tuple_fraction,
+                                List *refnames_tlist, List **sortClauses);
 static Plan *generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
                       List *refnames_tlist, List **sortClauses);
 static List *recurse_union_children(Node *setOp, PlannerInfo *root,
-                      SetOperationStmt *top_union,
-                      List *refnames_tlist);
+                                   double tuple_fraction,
+                                   SetOperationStmt *top_union,
+                                   List *refnames_tlist);
 static List *generate_setop_tlist(List *colTypes, int flag,
                     Index varno,
                     bool hack_constants,
@@ -85,11 +88,17 @@ static List *adjust_inherited_tlist(List *tlist,
  * 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, List **sortClauses)
+plan_set_operations(PlannerInfo *root, double tuple_fraction,
+                   List **sortClauses)
 {
    Query      *parse = root->parse;
    SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations;
@@ -124,7 +133,7 @@ plan_set_operations(PlannerInfo *root, List **sortClauses)
     * 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,
+   return recurse_set_operations((Node *) topop, root, tuple_fraction,
                                  topop->colTypes, true, -1,
                                  leftmostQuery->targetList,
                                  sortClauses);
@@ -134,6 +143,7 @@ plan_set_operations(PlannerInfo *root, List **sortClauses)
  * recurse_set_operations
  *   Recursively handle one step in a tree of set operations
  *
+ * tuple_fraction: fraction of tuples we expect to retrieve from node
  * colTypes: list of type OIDs of expected output columns
  * junkOK: if true, child resjunk columns may be left in the result
  * flag: if >= 0, add a resjunk output column indicating value of flag
@@ -142,6 +152,7 @@ plan_set_operations(PlannerInfo *root, List **sortClauses)
  */
 static Plan *
 recurse_set_operations(Node *setOp, PlannerInfo *root,
+                      double tuple_fraction,
                       List *colTypes, bool junkOK,
                       int flag, List *refnames_tlist,
                       List **sortClauses)
@@ -159,7 +170,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
        /*
         * Generate plan for primitive subquery
         */
-       subplan = subquery_planner(subquery, 0.0 /* default case */, NULL);
+       subplan = subquery_planner(subquery, tuple_fraction, NULL);
 
        /*
         * Add a SubqueryScan with the caller-requested targetlist
@@ -189,10 +200,12 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 
        /* UNIONs are much different from INTERSECT/EXCEPT */
        if (op->op == SETOP_UNION)
-           plan = generate_union_plan(op, root, refnames_tlist,
+           plan = generate_union_plan(op, root, tuple_fraction,
+                                      refnames_tlist,
                                       sortClauses);
        else
-           plan = generate_nonunion_plan(op, root, refnames_tlist,
+           plan = generate_nonunion_plan(op, root,
+                                         refnames_tlist,
                                          sortClauses);
 
        /*
@@ -235,6 +248,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
  */
 static Plan *
 generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
+                   double tuple_fraction,
                    List *refnames_tlist,
                    List **sortClauses)
 {
@@ -242,6 +256,20 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
    List       *tlist;
    Plan       *plan;
 
+   /*
+    * If plain UNION, tell children to fetch all tuples.
+    *
+    * Note: in UNION ALL, we pass the top-level tuple_fraction unmodified
+    * to each arm of the UNION ALL.  One could make a case for reducing
+    * the tuple fraction for later arms (discounting by the expected size
+    * of the earlier arms' results) but it seems not worth the trouble.
+    * The normal case where tuple_fraction isn't already zero is a LIMIT
+    * at top level, and passing it down as-is is usually enough to get the
+    * desired result of preferring fast-start plans.
+    */
+   if (!op->all)
+       tuple_fraction = 0.0;
+
    /*
     * 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
@@ -249,8 +277,10 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
     * such nodes and compute their children's plans.
     */
    planlist = list_concat(recurse_union_children(op->larg, root,
+                                                 tuple_fraction,
                                                  op, refnames_tlist),
                           recurse_union_children(op->rarg, root,
+                                                 tuple_fraction,
                                                  op, refnames_tlist));
 
    /*
@@ -309,10 +339,12 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
 
    /* Recurse on children, ensuring their outputs are marked */
    lplan = recurse_set_operations(op->larg, root,
+                                  0.0 /* all tuples needed */,
                                   op->colTypes, false, 0,
                                   refnames_tlist,
                                   &child_sortclauses);
    rplan = recurse_set_operations(op->rarg, root,
+                                  0.0 /* all tuples needed */,
                                   op->colTypes, false, 1,
                                   refnames_tlist,
                                   &child_sortclauses);
@@ -377,6 +409,7 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
  */
 static List *
 recurse_union_children(Node *setOp, PlannerInfo *root,
+                      double tuple_fraction,
                       SetOperationStmt *top_union,
                       List *refnames_tlist)
 {
@@ -392,9 +425,11 @@ recurse_union_children(Node *setOp, PlannerInfo *root,
        {
            /* 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));
        }
@@ -411,6 +446,7 @@ recurse_union_children(Node *setOp, PlannerInfo *root,
     * resjunk anyway.
     */
    return list_make1(recurse_set_operations(setOp, root,
+                                            tuple_fraction,
                                             top_union->colTypes, false,
                                             -1, refnames_tlist,
                                             &child_sortclauses));
index e4b9cb2f45c20f98fbbeb13eb8fa034aaca80500..35907e6e72aa25f782a508a73558872d742facf3 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.50 2005/06/05 22:32:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.51 2005/06/10 02:21:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,7 +45,8 @@ extern List *preprocess_targetlist(PlannerInfo *root, List *tlist);
 /*
  * prototypes for prepunion.c
  */
-extern Plan *plan_set_operations(PlannerInfo *root, List **sortClauses);
+extern Plan *plan_set_operations(PlannerInfo *root, double tuple_fraction,
+                                List **sortClauses);
 
 extern List *find_all_inheritors(Oid parentrel);