*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
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);
/*
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.
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?
*/
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
} 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,
* 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;
* 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);
* 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
*/
static Plan *
recurse_set_operations(Node *setOp, PlannerInfo *root,
+ double tuple_fraction,
List *colTypes, bool junkOK,
int flag, List *refnames_tlist,
List **sortClauses)
/*
* 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
/* 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);
/*
*/
static Plan *
generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
+ double tuple_fraction,
List *refnames_tlist,
List **sortClauses)
{
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
* 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));
/*
/* 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);
*/
static List *
recurse_union_children(Node *setOp, PlannerInfo *root,
+ double tuple_fraction,
SetOperationStmt *top_union,
List *refnames_tlist)
{
{
/* 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));
}
* resjunk anyway.
*/
return list_make1(recurse_set_operations(setOp, root,
+ tuple_fraction,
top_union->colTypes, false,
-1, refnames_tlist,
&child_sortclauses));