diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 76ffe04078..df8d0556b4 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.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? */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 2139ac23f1..1fedd9791c 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.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)); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index e4b9cb2f45..35907e6e72 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -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);