Skip to content

Commit 6fbc323

Browse files
committed
Further fallout from the MergeAppend patch.
Fix things so that top-N sorting can be used in child Sort nodes of a MergeAppend node, when there is a LIMIT and no intervening joins or grouping. Actually doing this on the executor side isn't too bad, but it's a bit messier to get the planner to cost it properly. Per gripe from Robert Haas. In passing, fix an oversight in the original top-N-sorting patch: query_planner should not assume that a LIMIT can be used to make an explicit sort cheaper when there will be grouping or aggregation in between. Possibly this should be back-patched, but I'm not sure the mistake is serious enough to be a real problem in practice.
1 parent 45768d1 commit 6fbc323

File tree

7 files changed

+103
-21
lines changed

7 files changed

+103
-21
lines changed

src/backend/executor/nodeLimit.c

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "executor/nodeLimit.h"
2626

2727
static void recompute_limits(LimitState *node);
28+
static void pass_down_bound(LimitState *node, PlanState *child_node);
2829

2930

3031
/* ----------------------------------------------------------------
@@ -293,26 +294,35 @@ recompute_limits(LimitState *node)
293294
/* Set state-machine state */
294295
node->lstate = LIMIT_RESCAN;
295296

296-
/*
297-
* If we have a COUNT, and our input is a Sort node, notify it that it can
298-
* use bounded sort.
299-
*
300-
* This is a bit of a kluge, but we don't have any more-abstract way of
301-
* communicating between the two nodes; and it doesn't seem worth trying
302-
* to invent one without some more examples of special communication
303-
* needs.
304-
*
305-
* Note: it is the responsibility of nodeSort.c to react properly to
306-
* changes of these parameters. If we ever do redesign this, it'd be a
307-
* good idea to integrate this signaling with the parameter-change
308-
* mechanism.
309-
*/
310-
if (IsA(outerPlanState(node), SortState))
297+
/* Notify child node about limit, if useful */
298+
pass_down_bound(node, outerPlanState(node));
299+
}
300+
301+
/*
302+
* If we have a COUNT, and our input is a Sort node, notify it that it can
303+
* use bounded sort. Also, if our input is a MergeAppend, we can apply the
304+
* same bound to any Sorts that are direct children of the MergeAppend,
305+
* since the MergeAppend surely need read no more than that many tuples from
306+
* any one input. We also have to be prepared to look through a Result,
307+
* since the planner might stick one atop MergeAppend for projection purposes.
308+
*
309+
* This is a bit of a kluge, but we don't have any more-abstract way of
310+
* communicating between the two nodes; and it doesn't seem worth trying
311+
* to invent one without some more examples of special communication needs.
312+
*
313+
* Note: it is the responsibility of nodeSort.c to react properly to
314+
* changes of these parameters. If we ever do redesign this, it'd be a
315+
* good idea to integrate this signaling with the parameter-change mechanism.
316+
*/
317+
static void
318+
pass_down_bound(LimitState *node, PlanState *child_node)
319+
{
320+
if (IsA(child_node, SortState))
311321
{
312-
SortState *sortState = (SortState *) outerPlanState(node);
322+
SortState *sortState = (SortState *) child_node;
313323
int64 tuples_needed = node->count + node->offset;
314324

315-
/* negative test checks for overflow */
325+
/* negative test checks for overflow in sum */
316326
if (node->noCount || tuples_needed < 0)
317327
{
318328
/* make sure flag gets reset if needed upon rescan */
@@ -324,6 +334,19 @@ recompute_limits(LimitState *node)
324334
sortState->bound = tuples_needed;
325335
}
326336
}
337+
else if (IsA(child_node, MergeAppendState))
338+
{
339+
MergeAppendState *maState = (MergeAppendState *) child_node;
340+
int i;
341+
342+
for (i = 0; i < maState->ms_nplans; i++)
343+
pass_down_bound(node, maState->mergeplans[i]);
344+
}
345+
else if (IsA(child_node, ResultState))
346+
{
347+
if (outerPlanState(child_node))
348+
pass_down_bound(node, outerPlanState(child_node));
349+
}
327350
}
328351

329352
/* ----------------------------------------------------------------

src/backend/nodes/outfuncs.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,7 @@ _outMergeAppendPath(StringInfo str, MergeAppendPath *node)
14931493
_outPathInfo(str, (Path *) node);
14941494

14951495
WRITE_NODE_FIELD(subpaths);
1496+
WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
14961497
}
14971498

14981499
static void
@@ -1611,6 +1612,7 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
16111612
WRITE_NODE_FIELD(minmax_aggs);
16121613
WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
16131614
WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
1615+
WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
16141616
WRITE_BOOL_FIELD(hasInheritedTarget);
16151617
WRITE_BOOL_FIELD(hasJoinRTEs);
16161618
WRITE_BOOL_FIELD(hasHavingQual);

src/backend/optimizer/plan/createplan.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
714714
if (!pathkeys_contained_in(pathkeys, subpath->pathkeys))
715715
subplan = (Plan *) make_sort(root, subplan, numsortkeys,
716716
sortColIdx, sortOperators, nullsFirst,
717-
-1.0);
717+
best_path->limit_tuples);
718718

719719
subplans = lappend(subplans, subplan);
720720
}

src/backend/optimizer/plan/planmain.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ query_planner(PlannerInfo *root, List *tlist,
101101
ListCell *lc;
102102
double total_pages;
103103

104-
/* Make tuple_fraction accessible to lower-level routines */
104+
/* Make tuple_fraction, limit_tuples accessible to lower-level routines */
105105
root->tuple_fraction = tuple_fraction;
106+
root->limit_tuples = limit_tuples;
106107

107108
*num_groups = 1; /* default result */
108109

@@ -315,6 +316,9 @@ query_planner(PlannerInfo *root, List *tlist,
315316
!pathkeys_contained_in(root->distinct_pathkeys, root->group_pathkeys) ||
316317
!pathkeys_contained_in(root->window_pathkeys, root->group_pathkeys))
317318
tuple_fraction = 0.0;
319+
320+
/* In any case, limit_tuples shouldn't be specified here */
321+
Assert(limit_tuples < 0);
318322
}
319323
else if (parse->hasAggs || root->hasHavingQual)
320324
{
@@ -323,6 +327,9 @@ query_planner(PlannerInfo *root, List *tlist,
323327
* it will deliver a single result row (so leave *num_groups 1).
324328
*/
325329
tuple_fraction = 0.0;
330+
331+
/* limit_tuples shouldn't be specified here */
332+
Assert(limit_tuples < 0);
326333
}
327334
else if (parse->distinctClause)
328335
{
@@ -347,6 +354,9 @@ query_planner(PlannerInfo *root, List *tlist,
347354
*/
348355
if (tuple_fraction >= 1.0)
349356
tuple_fraction /= *num_groups;
357+
358+
/* limit_tuples shouldn't be specified here */
359+
Assert(limit_tuples < 0);
350360
}
351361
else
352362
{

src/backend/optimizer/plan/planner.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
968968
{
969969
/* No set operations, do regular planning */
970970
List *sub_tlist;
971+
double sub_limit_tuples;
971972
AttrNumber *groupColIdx = NULL;
972973
bool need_tlist_eval = true;
973974
QualCost tlist_cost;
@@ -1119,13 +1120,28 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
11191120
else
11201121
root->query_pathkeys = NIL;
11211122

1123+
/*
1124+
* Figure out whether there's a hard limit on the number of rows that
1125+
* query_planner's result subplan needs to return. Even if we know a
1126+
* hard limit overall, it doesn't apply if the query has any
1127+
* grouping/aggregation operations.
1128+
*/
1129+
if (parse->groupClause ||
1130+
parse->distinctClause ||
1131+
parse->hasAggs ||
1132+
parse->hasWindowFuncs ||
1133+
root->hasHavingQual)
1134+
sub_limit_tuples = -1.0;
1135+
else
1136+
sub_limit_tuples = limit_tuples;
1137+
11221138
/*
11231139
* Generate the best unsorted and presorted paths for this Query (but
11241140
* note there may not be any presorted path). query_planner will also
11251141
* estimate the number of groups in the query, and canonicalize all
11261142
* the pathkeys.
11271143
*/
1128-
query_planner(root, sub_tlist, tuple_fraction, limit_tuples,
1144+
query_planner(root, sub_tlist, tuple_fraction, sub_limit_tuples,
11291145
&cheapest_path, &sorted_path, &dNumGroups);
11301146

11311147
/*

src/backend/optimizer/util/pathnode.c

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,35 @@ create_merge_append_path(PlannerInfo *root,
694694
pathnode->path.pathkeys = pathkeys;
695695
pathnode->subpaths = subpaths;
696696

697+
/*
698+
* Apply query-wide LIMIT if known and path is for sole base relation.
699+
* Finding out the latter at this low level is a bit klugy.
700+
*/
701+
pathnode->limit_tuples = root->limit_tuples;
702+
if (pathnode->limit_tuples >= 0)
703+
{
704+
Index rti;
705+
706+
for (rti = 1; rti < root->simple_rel_array_size; rti++)
707+
{
708+
RelOptInfo *brel = root->simple_rel_array[rti];
709+
710+
if (brel == NULL)
711+
continue;
712+
713+
/* ignore RTEs that are "other rels" */
714+
if (brel->reloptkind != RELOPT_BASEREL)
715+
continue;
716+
717+
if (brel != rel)
718+
{
719+
/* Oops, it's a join query */
720+
pathnode->limit_tuples = -1.0;
721+
break;
722+
}
723+
}
724+
}
725+
697726
/* Add up all the costs of the input paths */
698727
input_startup_cost = 0;
699728
input_total_cost = 0;
@@ -720,7 +749,7 @@ create_merge_append_path(PlannerInfo *root,
720749
subpath->parent->width,
721750
0.0,
722751
work_mem,
723-
-1.0);
752+
pathnode->limit_tuples);
724753
input_startup_cost += sort_path.startup_cost;
725754
input_total_cost += sort_path.total_cost;
726755
}

src/include/nodes/relation.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ typedef struct PlannerInfo
198198
double total_table_pages; /* # of pages in all tables of query */
199199

200200
double tuple_fraction; /* tuple_fraction passed to query_planner */
201+
double limit_tuples; /* limit_tuples passed to query_planner */
201202

202203
bool hasInheritedTarget; /* true if parse->resultRelation is an
203204
* inheritance child rel */
@@ -766,6 +767,7 @@ typedef struct MergeAppendPath
766767
{
767768
Path path;
768769
List *subpaths; /* list of component Paths */
770+
double limit_tuples; /* hard limit on output tuples, or -1 */
769771
} MergeAppendPath;
770772

771773
/*

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy