diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index f0aabc9dc8..3d91694924 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.89 2002/10/14 04:26:54 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.90 2002/11/06 00:00:43 tgl Exp $ * */ @@ -275,7 +275,21 @@ explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan, pname = "Group"; break; case T_Agg: - pname = "Aggregate"; + switch (((Agg *) plan)->aggstrategy) + { + case AGG_PLAIN: + pname = "Aggregate"; + break; + case AGG_SORTED: + pname = "GroupAggregate"; + break; + case AGG_HASHED: + pname = "HashAggregate"; + break; + default: + pname = "Aggregate ???"; + break; + } break; case T_Unique: pname = "Unique"; diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index bf4a9bbbda..7714a68090 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -46,7 +46,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.90 2002/11/01 19:33:09 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.91 2002/11/06 00:00:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -58,6 +58,7 @@ #include "catalog/pg_operator.h" #include "executor/executor.h" #include "executor/nodeAgg.h" +#include "executor/nodeGroup.h" #include "miscadmin.h" #include "optimizer/clauses.h" #include "parser/parse_coerce.h" @@ -159,6 +160,7 @@ typedef struct AggStatePerAggData static void initialize_aggregate(AggStatePerAgg peraggstate); static void advance_transition_function(AggStatePerAgg peraggstate, Datum newVal, bool isNull); +static void advance_aggregates(AggState *aggstate, ExprContext *econtext); static void process_sorted_aggregate(AggState *aggstate, AggStatePerAgg peraggstate); static void finalize_aggregate(AggStatePerAgg peraggstate, @@ -313,6 +315,62 @@ advance_transition_function(AggStatePerAgg peraggstate, peraggstate->transValueIsNull = fcinfo.isnull; } +/* + * Advance all the aggregates for one input tuple. The input tuple + * has been stored in econtext->ecxt_scantuple, so that it is accessible + * to ExecEvalExpr. + * + * When called, CurrentMemoryContext should be the per-query context. + */ +static void +advance_aggregates(AggState *aggstate, ExprContext *econtext) +{ + MemoryContext oldContext; + int aggno; + + /* + * Clear and select the current working context for evaluation + * of the input expressions and transition functions at this + * input tuple. + */ + econtext->ecxt_per_tuple_memory = aggstate->agg_cxt[aggstate->which_cxt]; + ResetExprContext(econtext); + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + for (aggno = 0; aggno < aggstate->numaggs; aggno++) + { + AggStatePerAgg peraggstate = &aggstate->peragg[aggno]; + Aggref *aggref = peraggstate->aggref; + Datum newVal; + bool isNull; + + newVal = ExecEvalExpr(aggref->target, econtext, &isNull, NULL); + + if (aggref->aggdistinct) + { + /* in DISTINCT mode, we may ignore nulls */ + if (isNull) + continue; + /* putdatum has to be called in per-query context */ + MemoryContextSwitchTo(oldContext); + tuplesort_putdatum(peraggstate->sortstate, newVal, isNull); + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + } + else + { + advance_transition_function(peraggstate, newVal, isNull); + } + } + + /* + * Make the other context current so that these transition + * results are preserved. + */ + aggstate->which_cxt = 1 - aggstate->which_cxt; + + MemoryContextSwitchTo(oldContext); +} + /* * Run the transition function for a DISTINCT aggregate. This is called * after we have completed entering all the input values into the sort @@ -448,23 +506,18 @@ finalize_aggregate(AggStatePerAgg peraggstate, } -/* --------------------------------------- - * +/* * ExecAgg - * * ExecAgg receives tuples from its outer subplan and aggregates over * the appropriate attribute for each aggregate function use (Aggref * node) appearing in the targetlist or qual of the node. The number - * of tuples to aggregate over depends on whether a GROUP BY clause is - * present. We can produce an aggregate result row per group, or just - * one for the whole query. The value of each aggregate is stored in - * the expression context to be used when ExecProject evaluates the - * result tuple. - * - * If the outer subplan is a Group node, ExecAgg returns as many tuples - * as there are groups. - * - * ------------------------------------------ + * of tuples to aggregate over depends on whether grouped or plain + * aggregation is selected. In grouped aggregation, we produce a result + * row for each group; in plain aggregation there's a single result row + * for the whole query. In either case, the value of each aggregate is + * stored in the expression context to be used when ExecProject evaluates + * the result tuple. */ TupleTableSlot * ExecAgg(Agg *node) @@ -478,10 +531,10 @@ ExecAgg(Agg *node) bool *aggnulls; AggStatePerAgg peragg; MemoryContext oldContext; + TupleTableSlot *outerslot; + TupleTableSlot *firstSlot; TupleTableSlot *resultSlot; - HeapTuple inputTuple; int aggno; - bool isNull; /* * get state info from node @@ -494,6 +547,7 @@ ExecAgg(Agg *node) aggnulls = econtext->ecxt_aggnulls; projInfo = aggstate->csstate.cstate.cs_ProjInfo; peragg = aggstate->peragg; + firstSlot = aggstate->csstate.css_ScanTupleSlot; /* * We loop retrieving groups until we find one matching @@ -504,6 +558,31 @@ ExecAgg(Agg *node) if (aggstate->agg_done) return NULL; + /* + * If we don't already have the first tuple of the new group, + * fetch it from the outer plan. + */ + if (aggstate->grp_firstTuple == NULL) + { + outerslot = ExecProcNode(outerPlan, (Plan *) node); + if (!TupIsNull(outerslot)) + { + /* + * Make a copy of the first input tuple; we will use this + * for comparisons (in group mode) and for projection. + */ + aggstate->grp_firstTuple = heap_copytuple(outerslot->val); + } + else + { + /* outer plan produced no tuples at all */ + aggstate->agg_done = true; + /* If we are grouping, we should produce no tuples too */ + if (node->aggstrategy != AGG_PLAIN) + return NULL; + } + } + /* * Clear the per-output-tuple context for each group */ @@ -519,74 +598,61 @@ ExecAgg(Agg *node) initialize_aggregate(peraggstate); } - inputTuple = NULL; /* no saved input tuple yet */ - - /* - * for each tuple from the outer plan, update all the aggregates - */ - for (;;) + if (aggstate->grp_firstTuple != NULL) { - TupleTableSlot *outerslot; + /* + * Store the copied first input tuple in the tuple table slot + * reserved for it. The tuple will be deleted when it is + * cleared from the slot. + */ + ExecStoreTuple(aggstate->grp_firstTuple, + firstSlot, + InvalidBuffer, + true); + aggstate->grp_firstTuple = NULL; /* don't keep two pointers */ - outerslot = ExecProcNode(outerPlan, (Plan *) node); - if (TupIsNull(outerslot)) - break; - econtext->ecxt_scantuple = outerslot; + /* set up for first advance_aggregates call */ + econtext->ecxt_scantuple = firstSlot; /* - * Clear and select the current working context for evaluation - * of the input expressions and transition functions at this - * input tuple. + * Process each outer-plan tuple, and then fetch the next one, + * until we exhaust the outer plan or cross a group boundary. */ - econtext->ecxt_per_tuple_memory = - aggstate->agg_cxt[aggstate->which_cxt]; - ResetExprContext(econtext); - oldContext = - MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - - for (aggno = 0; aggno < aggstate->numaggs; aggno++) + for (;;) { - AggStatePerAgg peraggstate = &peragg[aggno]; - Aggref *aggref = peraggstate->aggref; - Datum newVal; + advance_aggregates(aggstate, econtext); - newVal = ExecEvalExpr(aggref->target, econtext, - &isNull, NULL); - - if (aggref->aggdistinct) + outerslot = ExecProcNode(outerPlan, (Plan *) node); + if (TupIsNull(outerslot)) { - /* in DISTINCT mode, we may ignore nulls */ - if (isNull) - continue; - /* putdatum has to be called in per-query context */ - MemoryContextSwitchTo(oldContext); - tuplesort_putdatum(peraggstate->sortstate, - newVal, isNull); - MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + /* no more outer-plan tuples available */ + aggstate->agg_done = true; + break; } - else + /* set up for next advance_aggregates call */ + econtext->ecxt_scantuple = outerslot; + + /* + * If we are grouping, check whether we've crossed a group + * boundary. + */ + if (node->aggstrategy == AGG_SORTED) { - advance_transition_function(peraggstate, - newVal, isNull); + if (!execTuplesMatch(firstSlot->val, + outerslot->val, + firstSlot->ttc_tupleDescriptor, + node->numCols, node->grpColIdx, + aggstate->eqfunctions, + aggstate->agg_cxt[aggstate->which_cxt])) + { + /* + * Save the first input tuple of the next group. + */ + aggstate->grp_firstTuple = heap_copytuple(outerslot->val); + break; + } } } - - /* - * Make the other context current so that these transition - * results are preserved. - */ - aggstate->which_cxt = 1 - aggstate->which_cxt; - - MemoryContextSwitchTo(oldContext); - - /* - * Keep a copy of the first input tuple for the projection. - * (We only need one since only the GROUP BY columns in it can - * be referenced, and these will be the same for all tuples - * aggregated over.) - */ - if (!inputTuple) - inputTuple = heap_copytuple(outerslot->val); } /* @@ -626,77 +692,50 @@ ExecAgg(Agg *node) } /* - * If the outerPlan is a Group node, we will reach here after each - * group. We are not done unless the Group node is done (a little - * ugliness here while we reach into the Group's state to find - * out). Furthermore, when grouping we return nothing at all - * unless we had some input tuple(s). By the nature of Group, - * there are no empty groups, so if we get here with no input the - * whole scan is empty. + * If we have no first tuple (ie, the outerPlan didn't return + * anything), create a dummy all-nulls input tuple for use by + * ExecProject. 99.44% of the time this is a waste of cycles, + * because ordinarily the projected output tuple's targetlist + * cannot contain any direct (non-aggregated) references to + * input columns, so the dummy tuple will not be referenced. + * However there are special cases where this isn't so --- in + * particular an UPDATE involving an aggregate will have a + * targetlist reference to ctid. We need to return a null for + * ctid in that situation, not coredump. * - * If the outerPlan isn't a Group, we are done when we get here, and - * we will emit a (single) tuple even if there were no input - * tuples. + * The values returned for the aggregates will be the initial + * values of the transition functions. */ - if (IsA(outerPlan, Group)) + if (TupIsNull(firstSlot)) { - /* aggregation over groups */ - aggstate->agg_done = ((Group *) outerPlan)->grpstate->grp_done; - /* check for no groups */ - if (inputTuple == NULL) - return NULL; - } - else - { - aggstate->agg_done = true; + TupleDesc tupType; - /* - * If inputtuple==NULL (ie, the outerPlan didn't return - * anything), create a dummy all-nulls input tuple for use by - * ExecProject. 99.44% of the time this is a waste of cycles, - * because ordinarily the projected output tuple's targetlist - * cannot contain any direct (non-aggregated) references to - * input columns, so the dummy tuple will not be referenced. - * However there are special cases where this isn't so --- in - * particular an UPDATE involving an aggregate will have a - * targetlist reference to ctid. We need to return a null for - * ctid in that situation, not coredump. - * - * The values returned for the aggregates will be the initial - * values of the transition functions. - */ - if (inputTuple == NULL) + /* Should only happen in non-grouped mode */ + Assert(node->aggstrategy == AGG_PLAIN); + Assert(aggstate->agg_done); + + tupType = firstSlot->ttc_tupleDescriptor; + /* watch out for zero-column input tuples, though... */ + if (tupType && tupType->natts > 0) { - TupleDesc tupType; + HeapTuple nullsTuple; Datum *dvalues; char *dnulls; - tupType = aggstate->csstate.css_ScanTupleSlot->ttc_tupleDescriptor; - /* watch out for null input tuples, though... */ - if (tupType && tupType->natts > 0) - { - dvalues = (Datum *) palloc(sizeof(Datum) * tupType->natts); - dnulls = (char *) palloc(sizeof(char) * tupType->natts); - MemSet(dvalues, 0, sizeof(Datum) * tupType->natts); - MemSet(dnulls, 'n', sizeof(char) * tupType->natts); - inputTuple = heap_formtuple(tupType, dvalues, dnulls); - pfree(dvalues); - pfree(dnulls); - } + dvalues = (Datum *) palloc(sizeof(Datum) * tupType->natts); + dnulls = (char *) palloc(sizeof(char) * tupType->natts); + MemSet(dvalues, 0, sizeof(Datum) * tupType->natts); + MemSet(dnulls, 'n', sizeof(char) * tupType->natts); + nullsTuple = heap_formtuple(tupType, dvalues, dnulls); + ExecStoreTuple(nullsTuple, + firstSlot, + InvalidBuffer, + true); + pfree(dvalues); + pfree(dnulls); } } - /* - * Store the representative input tuple in the tuple table slot - * reserved for it. The tuple will be deleted when it is cleared - * from the slot. - */ - ExecStoreTuple(inputTuple, - aggstate->csstate.css_ScanTupleSlot, - InvalidBuffer, - true); - econtext->ecxt_scantuple = aggstate->csstate.css_ScanTupleSlot; - /* * Do projection and qual check in the per-output-tuple context. */ @@ -707,6 +746,7 @@ ExecAgg(Agg *node) * representative input tuple. Store it in the result tuple slot. * Note we do not support aggregates returning sets ... */ + econtext->ecxt_scantuple = firstSlot; resultSlot = ExecProject(projInfo, NULL); /* @@ -748,6 +788,8 @@ ExecInitAgg(Agg *node, EState *estate, Plan *parent) */ aggstate = makeNode(AggState); node->aggstate = aggstate; + aggstate->eqfunctions = NULL; + aggstate->grp_firstTuple = NULL; aggstate->agg_done = false; /* @@ -765,13 +807,12 @@ ExecInitAgg(Agg *node, EState *estate, Plan *parent) if (numaggs <= 0) { /* - * This used to be treated as an error, but we can't do that - * anymore because constant-expression simplification could - * optimize away all of the Aggrefs in the targetlist and qual. - * So, just make a debug note, and force numaggs positive so that - * palloc()s below don't choke. + * This is not an error condition: we might be using the Agg node just + * to do hash-based grouping. Even in the regular case, + * constant-expression simplification could optimize away all of the + * Aggrefs in the targetlist and qual. So keep going, but force local + * copy of numaggs positive so that palloc()s below don't choke. */ - elog(DEBUG1, "ExecInitAgg: could not find any aggregate functions"); numaggs = 1; } @@ -843,6 +884,17 @@ ExecInitAgg(Agg *node, EState *estate, Plan *parent) ExecAssignResultTypeFromTL((Plan *) node, &aggstate->csstate.cstate); ExecAssignProjectionInfo((Plan *) node, &aggstate->csstate.cstate); + /* + * If we are grouping, precompute fmgr lookup data for inner loop + */ + if (node->numCols > 0) + { + aggstate->eqfunctions = + execTuplesMatchPrepare(ExecGetScanType(&aggstate->csstate), + node->numCols, + node->grpColIdx); + } + /* * Perform lookups of aggregate function info, and initialize the * unchanging fields of the per-agg data @@ -1024,6 +1076,11 @@ ExecEndAgg(Agg *node) /* clean up tuple table */ ExecClearTuple(aggstate->csstate.css_ScanTupleSlot); + if (aggstate->grp_firstTuple != NULL) + { + heap_freetuple(aggstate->grp_firstTuple); + aggstate->grp_firstTuple = NULL; + } } void @@ -1033,6 +1090,11 @@ ExecReScanAgg(Agg *node, ExprContext *exprCtxt, Plan *parent) ExprContext *econtext = aggstate->csstate.cstate.cs_ExprContext; aggstate->agg_done = false; + if (aggstate->grp_firstTuple != NULL) + { + heap_freetuple(aggstate->grp_firstTuple); + aggstate->grp_firstTuple = NULL; + } MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * aggstate->numaggs); MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * aggstate->numaggs); diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c index 5d7f6a6992..662c3d4798 100644 --- a/src/backend/executor/nodeGroup.c +++ b/src/backend/executor/nodeGroup.c @@ -15,7 +15,7 @@ * locate group boundaries. * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeGroup.c,v 1.47 2002/06/20 20:29:28 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeGroup.c,v 1.48 2002/11/06 00:00:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -31,147 +31,14 @@ #include "utils/lsyscache.h" #include "utils/syscache.h" -static TupleTableSlot *ExecGroupEveryTuple(Group *node); -static TupleTableSlot *ExecGroupOneTuple(Group *node); -/* --------------------------------------- +/* * ExecGroup - * - * There are two modes in which tuples are returned by ExecGroup. If - * tuplePerGroup is TRUE, every tuple from the same group will be - * returned, followed by a NULL at the end of each group. This is - * useful for Agg node which needs to aggregate over tuples of the same - * group. (eg. SELECT salary, count(*) FROM emp GROUP BY salary) - * - * If tuplePerGroup is FALSE, only one tuple per group is returned. The - * tuple returned contains only the group columns. NULL is returned only - * at the end when no more groups are present. This is useful when - * the query does not involve aggregates. (eg. SELECT salary FROM emp - * GROUP BY salary) - * ------------------------------------------ + * Return one tuple for each group of matching input tuples. */ TupleTableSlot * ExecGroup(Group *node) -{ - if (node->tuplePerGroup) - return ExecGroupEveryTuple(node); - else - return ExecGroupOneTuple(node); -} - -/* - * ExecGroupEveryTuple - - * return every tuple with a NULL between each group - */ -static TupleTableSlot * -ExecGroupEveryTuple(Group *node) -{ - GroupState *grpstate; - EState *estate; - ExprContext *econtext; - TupleDesc tupdesc; - HeapTuple outerTuple = NULL; - HeapTuple firsttuple; - TupleTableSlot *outerslot; - ProjectionInfo *projInfo; - TupleTableSlot *resultSlot; - - /* - * get state info from node - */ - grpstate = node->grpstate; - if (grpstate->grp_done) - return NULL; - estate = node->plan.state; - econtext = grpstate->csstate.cstate.cs_ExprContext; - tupdesc = ExecGetScanType(&grpstate->csstate); - - /* - * We need not call ResetExprContext here because execTuplesMatch will - * reset the per-tuple memory context once per input tuple. - */ - - /* if we haven't returned first tuple of a new group yet ... */ - if (grpstate->grp_useFirstTuple) - { - grpstate->grp_useFirstTuple = FALSE; - - /* - * note we rely on subplan to hold ownership of the tuple for as - * long as we need it; we don't copy it. - */ - ExecStoreTuple(grpstate->grp_firstTuple, - grpstate->csstate.css_ScanTupleSlot, - InvalidBuffer, false); - } - else - { - outerslot = ExecProcNode(outerPlan(node), (Plan *) node); - if (TupIsNull(outerslot)) - { - grpstate->grp_done = TRUE; - return NULL; - } - outerTuple = outerslot->val; - - firsttuple = grpstate->grp_firstTuple; - if (firsttuple == NULL) - { - /* this should occur on the first call only */ - grpstate->grp_firstTuple = heap_copytuple(outerTuple); - } - else - { - /* - * Compare with first tuple and see if this tuple is of the - * same group. - */ - if (!execTuplesMatch(firsttuple, outerTuple, - tupdesc, - node->numCols, node->grpColIdx, - grpstate->eqfunctions, - econtext->ecxt_per_tuple_memory)) - { - /* - * No; save the tuple to return it next time, and return - * NULL - */ - grpstate->grp_useFirstTuple = TRUE; - heap_freetuple(firsttuple); - grpstate->grp_firstTuple = heap_copytuple(outerTuple); - - return NULL; /* signifies the end of the group */ - } - } - - /* - * note we rely on subplan to hold ownership of the tuple for as - * long as we need it; we don't copy it. - */ - ExecStoreTuple(outerTuple, - grpstate->csstate.css_ScanTupleSlot, - InvalidBuffer, false); - } - - /* - * form a projection tuple, store it in the result tuple slot and - * return it. - */ - projInfo = grpstate->csstate.cstate.cs_ProjInfo; - - econtext->ecxt_scantuple = grpstate->csstate.css_ScanTupleSlot; - resultSlot = ExecProject(projInfo, NULL); - - return resultSlot; -} - -/* - * ExecGroupOneTuple - - * returns one tuple per group, a NULL at the end when there are no more - * tuples. - */ -static TupleTableSlot * -ExecGroupOneTuple(Group *node) { GroupState *grpstate; EState *estate; @@ -198,10 +65,11 @@ ExecGroupOneTuple(Group *node) * reset the per-tuple memory context once per input tuple. */ + /* If we don't already have first tuple of group, fetch it */ + /* this should occur on the first call only */ firsttuple = grpstate->grp_firstTuple; if (firsttuple == NULL) { - /* this should occur on the first call only */ outerslot = ExecProcNode(outerPlan(node), (Plan *) node); if (TupIsNull(outerslot)) { @@ -213,7 +81,7 @@ ExecGroupOneTuple(Group *node) } /* - * find all tuples that belong to a group + * Scan over all tuples that belong to this group */ for (;;) { @@ -239,22 +107,18 @@ ExecGroupOneTuple(Group *node) } /* - * form a projection tuple, store it in the result tuple slot and - * return it. - */ - projInfo = grpstate->csstate.cstate.cs_ProjInfo; - - /* - * note we rely on subplan to hold ownership of the tuple for as long - * as we need it; we don't copy it. + * form a projection tuple based on the (copied) first tuple of the + * group, and store it in the result tuple slot. */ ExecStoreTuple(firsttuple, grpstate->csstate.css_ScanTupleSlot, - InvalidBuffer, false); + InvalidBuffer, + false); econtext->ecxt_scantuple = grpstate->csstate.css_ScanTupleSlot; + projInfo = grpstate->csstate.cstate.cs_ProjInfo; resultSlot = ExecProject(projInfo, NULL); - /* save outerTuple if we are not done yet */ + /* save first tuple of next group, if we are not done yet */ if (!grpstate->grp_done) { heap_freetuple(firsttuple); @@ -386,14 +250,14 @@ ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent) } /***************************************************************************** - * Code shared with nodeUnique.c + * Code shared with nodeUnique.c and nodeAgg.c *****************************************************************************/ /* * execTuplesMatch * Return true if two tuples match in all the indicated fields. - * This is used to detect group boundaries in nodeGroup, and to - * decide whether two tuples are distinct or not in nodeUnique. + * This is used to detect group boundaries in nodeGroup and nodeAgg, + * and to decide whether two tuples are distinct or not in nodeUnique. * * tuple1, tuple2: the tuples to compare * tupdesc: tuple descriptor applying to both tuples @@ -425,7 +289,8 @@ execTuplesMatch(HeapTuple tuple1, * We cannot report a match without checking all the fields, but we * can report a non-match as soon as we find unequal fields. So, * start comparing at the last field (least significant sort key). - * That's the most likely to be different... + * That's the most likely to be different if we are dealing with + * sorted input. */ result = true; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 5fff2f762a..0438e0ce60 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.214 2002/10/14 22:14:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.215 2002/11/06 00:00:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -497,10 +497,10 @@ _copyGroup(Group *from) CopyPlanFields((Plan *) from, (Plan *) newnode); - newnode->tuplePerGroup = from->tuplePerGroup; newnode->numCols = from->numCols; newnode->grpColIdx = palloc(from->numCols * sizeof(AttrNumber)); - memcpy(newnode->grpColIdx, from->grpColIdx, from->numCols * sizeof(AttrNumber)); + memcpy(newnode->grpColIdx, from->grpColIdx, + from->numCols * sizeof(AttrNumber)); return newnode; } @@ -516,6 +516,15 @@ _copyAgg(Agg *from) CopyPlanFields((Plan *) from, (Plan *) newnode); + newnode->aggstrategy = from->aggstrategy; + newnode->numCols = from->numCols; + if (from->numCols > 0) + { + newnode->grpColIdx = palloc(from->numCols * sizeof(AttrNumber)); + memcpy(newnode->grpColIdx, from->grpColIdx, + from->numCols * sizeof(AttrNumber)); + } + return newnode; } @@ -1280,6 +1289,29 @@ _copyAppendPath(AppendPath *from) return newnode; } +/* ---------------- + * _copyResultPath + * ---------------- + */ +static ResultPath * +_copyResultPath(ResultPath *from) +{ + ResultPath *newnode = makeNode(ResultPath); + + /* + * copy the node superclass fields + */ + CopyPathFields((Path *) from, (Path *) newnode); + + /* + * copy remainder of node + */ + Node_Copy(from, newnode, subpath); + Node_Copy(from, newnode, constantqual); + + return newnode; +} + /* ---------------- * CopyJoinPathFields * @@ -2878,6 +2910,9 @@ copyObject(void *from) case T_AppendPath: retval = _copyAppendPath(from); break; + case T_ResultPath: + retval = _copyResultPath(from); + break; case T_NestPath: retval = _copyNestPath(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 551c32d5db..122de38fed 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.161 2002/10/14 22:14:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.162 2002/11/06 00:00:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -463,6 +463,18 @@ _equalAppendPath(AppendPath *a, AppendPath *b) return true; } +static bool +_equalResultPath(ResultPath *a, ResultPath *b) +{ + if (!_equalPath((Path *) a, (Path *) b)) + return false; + if (!equal(a->subpath, b->subpath)) + return false; + if (!equal(a->constantqual, b->constantqual)) + return false; + return true; +} + static bool _equalJoinPath(JoinPath *a, JoinPath *b) { @@ -2103,6 +2115,9 @@ equal(void *a, void *b) case T_AppendPath: retval = _equalAppendPath(a, b); break; + case T_ResultPath: + retval = _equalResultPath(a, b); + break; case T_IndexOptInfo: retval = _equalIndexOptInfo(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index e1a34118a6..2d6db222b2 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.176 2002/10/14 22:14:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.177 2002/11/06 00:00:44 tgl Exp $ * * NOTES * Every (plan) node in POSTGRES has an associated "out" routine which @@ -597,6 +597,8 @@ _outAgg(StringInfo str, Agg *node) { appendStringInfo(str, " AGG "); _outPlanInfo(str, (Plan *) node); + appendStringInfo(str, " :aggstrategy %d :numCols %d ", + (int) node->aggstrategy, node->numCols); } static void @@ -604,11 +606,7 @@ _outGroup(StringInfo str, Group *node) { appendStringInfo(str, " GRP "); _outPlanInfo(str, (Plan *) node); - - /* the actual Group fields */ - appendStringInfo(str, " :numCols %d :tuplePerGroup %s ", - node->numCols, - booltostr(node->tuplePerGroup)); + appendStringInfo(str, " :numCols %d ", node->numCols); } static void @@ -1114,6 +1112,26 @@ _outAppendPath(StringInfo str, AppendPath *node) _outNode(str, node->subpaths); } +/* + * ResultPath is a subclass of Path. + */ +static void +_outResultPath(StringInfo str, ResultPath *node) +{ + appendStringInfo(str, + " RESULTPATH :pathtype %d :startup_cost %.2f :total_cost %.2f :pathkeys ", + node->path.pathtype, + node->path.startup_cost, + node->path.total_cost); + _outNode(str, node->path.pathkeys); + + appendStringInfo(str, " :subpath "); + _outNode(str, node->subpath); + + appendStringInfo(str, " :constantqual "); + _outNode(str, node->constantqual); +} + /* * NestPath is a subclass of Path */ @@ -1717,6 +1735,9 @@ _outNode(StringInfo str, void *obj) case T_AppendPath: _outAppendPath(str, obj); break; + case T_ResultPath: + _outResultPath(str, obj); + break; case T_NestPath: _outNestPath(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 33e2841343..568bf8ee1e 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.135 2002/10/14 22:14:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.136 2002/11/06 00:00:44 tgl Exp $ * * NOTES * Most of the read functions for plan nodes are tested. (In fact, they @@ -696,17 +696,6 @@ _readSort(void) return local_node; } -static Agg * -_readAgg(void) -{ - Agg *local_node; - - local_node = makeNode(Agg); - _getPlan((Plan *) local_node); - - return local_node; -} - /* ---------------- * _readHash * @@ -1880,6 +1869,45 @@ _readAppendPath(void) return local_node; } +/* ---------------- + * _readResultPath + * + * ResultPath is a subclass of Path. + * ---------------- + */ +static ResultPath * +_readResultPath(void) +{ + ResultPath *local_node; + char *token; + int length; + + local_node = makeNode(ResultPath); + + token = pg_strtok(&length); /* get :pathtype */ + token = pg_strtok(&length); /* now read it */ + local_node->path.pathtype = atoi(token); + + token = pg_strtok(&length); /* get :startup_cost */ + token = pg_strtok(&length); /* now read it */ + local_node->path.startup_cost = (Cost) atof(token); + + token = pg_strtok(&length); /* get :total_cost */ + token = pg_strtok(&length); /* now read it */ + local_node->path.total_cost = (Cost) atof(token); + + token = pg_strtok(&length); /* get :pathkeys */ + local_node->path.pathkeys = nodeRead(true); /* now read it */ + + token = pg_strtok(&length); /* get :subpath */ + local_node->subpath = nodeRead(true); /* now read it */ + + token = pg_strtok(&length); /* get :constantqual */ + local_node->constantqual = nodeRead(true); /* now read it */ + + return local_node; +} + /* ---------------- * _readNestPath * @@ -2196,8 +2224,6 @@ parsePlanString(void) return_value = _readFromExpr(); else if (length == 8 && strncmp(token, "JOINEXPR", length) == 0) return_value = _readJoinExpr(); - else if (length == 3 && strncmp(token, "AGG", length) == 0) - return_value = _readAgg(); else if (length == 4 && strncmp(token, "HASH", length) == 0) return_value = _readHash(); else if (length == 6 && strncmp(token, "RESDOM", length) == 0) @@ -2240,6 +2266,8 @@ parsePlanString(void) return_value = _readTidPath(); else if (length == 10 && strncmp(token, "APPENDPATH", length) == 0) return_value = _readAppendPath(); + else if (length == 10 && strncmp(token, "RESULTPATH", length) == 0) + return_value = _readResultPath(); else if (length == 8 && strncmp(token, "NESTPATH", length) == 0) return_value = _readNestPath(); else if (length == 9 && strncmp(token, "MERGEPATH", length) == 0) diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README index 26f2316886..698b831a9c 100644 --- a/src/backend/optimizer/README +++ b/src/backend/optimizer/README @@ -219,11 +219,9 @@ planner() pull out constant quals, which can be used to gate execution of the whole plan (if any are found, we make a top-level Result node to do the gating) - make a simplified target list that only contains Vars, no expressions ----subplanner() - make list of base relations used in query - split up the qual into restrictions (a=1) and joins (b=c) - find qual clauses that enable merge and hash joins + make list of base relations used in query + split up the qual into restrictions (a=1) and joins (b=c) + find qual clauses that enable merge and hash joins ----make_one_rel() set_base_rel_pathlist() find scan and all index paths for each base relation @@ -239,7 +237,7 @@ planner() cheapest path for each newly constructed joinrel. Loop back if this wasn't the top join level. Back at query_planner: - put back constant quals and non-simplified target list + put back any constant quals by adding a Result node Back at grouping_planner: do grouping(GROUP) do aggregates @@ -257,8 +255,11 @@ RelOptInfo - a relation or joined relations JoinInfo - join clauses, including the relids needed for the join Path - every way to generate a RelOptInfo(sequential,index,joins) - SeqScan - a plain Path node with nodeTag = T_SeqScan + SeqScan - a plain Path node with pathtype = T_SeqScan IndexPath - index scans + TidPath - scan by CTID + AppendPath - append multiple subpaths together + ResultPath - a Result plan (used for variable-free tlist or qual) NestPath - nested-loop joins MergePath - merge joins HashPath - hash joins @@ -276,10 +277,10 @@ generated during the optimization process are marked with their sort order It is also possible to avoid an explicit sort step to implement a user's ORDER BY clause if the final path has the right ordering already, so the -sort ordering is of interest even at the top level. subplanner() will +sort ordering is of interest even at the top level. query_planner() will look for the cheapest path with a sort order matching the desired order, -and will compare its cost to the cost of using the cheapest-overall path -and doing an explicit sort. +and grouping_planner() will compare its cost to the cost of using the +cheapest-overall path and doing an explicit sort. When we are generating paths for a particular RelOptInfo, we discard a path if it is more expensive than another known path that has the same or better diff --git a/src/backend/optimizer/geqo/geqo_misc.c b/src/backend/optimizer/geqo/geqo_misc.c index ef7b489f59..7cda46946b 100644 --- a/src/backend/optimizer/geqo/geqo_misc.c +++ b/src/backend/optimizer/geqo/geqo_misc.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: geqo_misc.c,v 1.34 2002/09/04 20:31:20 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/geqo/geqo_misc.c,v 1.35 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,19 +19,17 @@ =*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*= */ - - #include "postgres.h" #include "optimizer/geqo_misc.h" #include "nodes/print.h" + #ifdef GEQO_DEBUG -static float avg_pool(Pool *pool); -/* avg_pool - * +/* + * avg_pool */ static float avg_pool(Pool *pool) @@ -81,7 +79,6 @@ print_pool(FILE *fp, Pool *pool, int start, int stop) /* print_gen * * printout for chromosome: best, worst, mean, average - * */ void print_gen(FILE *fp, Pool *pool, int generation) @@ -121,133 +118,4 @@ print_edge_table(FILE *fp, Edge *edge_table, int num_gene) fprintf(fp, "\n"); } -/************************************************************* - Debug output subroutines - *************************************************************/ - -void -geqo_print_joinclauses(Query *root, List *clauses) -{ - List *l; - - foreach(l, clauses) - { - RestrictInfo *c = lfirst(l); - - print_expr((Node *) c->clause, root->rtable); - if (lnext(l)) - printf(" "); - } -} - -void -geqo_print_path(Query *root, Path *path, int indent) -{ - char *ptype = NULL; - JoinPath *jp; - bool join = false; - int i; - - for (i = 0; i < indent; i++) - printf("\t"); - - switch (nodeTag(path)) - { - case T_Path: - ptype = "SeqScan"; - join = false; - break; - case T_IndexPath: - ptype = "IdxScan"; - join = false; - break; - case T_NestPath: - ptype = "Nestloop"; - join = true; - break; - case T_MergePath: - ptype = "MergeJoin"; - join = true; - break; - case T_HashPath: - ptype = "HashJoin"; - join = true; - break; - default: - break; - } - if (join) - { - jp = (JoinPath *) path; - printf("%s rows=%.0f cost=%.2f..%.2f\n", - ptype, path->parent->rows, - path->startup_cost, path->total_cost); - switch (nodeTag(path)) - { - case T_MergePath: - case T_HashPath: - for (i = 0; i < indent + 1; i++) - printf("\t"); - printf(" clauses=("); - geqo_print_joinclauses(root, jp->joinrestrictinfo); - printf(")\n"); - - if (nodeTag(path) == T_MergePath) - { - MergePath *mp = (MergePath *) path; - - if (mp->outersortkeys || mp->innersortkeys) - { - for (i = 0; i < indent + 1; i++) - printf("\t"); - printf(" sortouter=%d sortinner=%d\n", - ((mp->outersortkeys) ? 1 : 0), - ((mp->innersortkeys) ? 1 : 0)); - } - } - break; - default: - break; - } - geqo_print_path(root, jp->outerjoinpath, indent + 1); - geqo_print_path(root, jp->innerjoinpath, indent + 1); - } - else - { - int relid = lfirsti(path->parent->relids); - - printf("%s(%d) rows=%.0f cost=%.2f..%.2f\n", - ptype, relid, path->parent->rows, - path->startup_cost, path->total_cost); - - if (IsA(path, IndexPath)) - { - printf(" pathkeys="); - print_pathkeys(path->pathkeys, root->rtable); - } - } -} - -void -geqo_print_rel(Query *root, RelOptInfo *rel) -{ - List *l; - - printf("______________________________\n"); - printf("("); - foreach(l, rel->relids) - printf("%d ", lfirsti(l)); - printf("): rows=%.0f width=%d\n", rel->rows, rel->width); - - printf("\tpath list:\n"); - foreach(l, rel->pathlist) - geqo_print_path(root, lfirst(l), 1); - - printf("\n\tcheapest startup path:\n"); - geqo_print_path(root, rel->cheapest_startup_path, 1); - - printf("\n\tcheapest total path:\n"); - geqo_print_path(root, rel->cheapest_total_path, 1); -} - #endif /* GEQO_DEBUG */ diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 7d8d6a6beb..ea016e8a2e 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.88 2002/09/04 20:31:20 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.89 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -742,6 +742,14 @@ print_path(Query *root, Path *path, int indent) ptype = "TidScan"; join = false; break; + case T_AppendPath: + ptype = "Append"; + join = false; + break; + case T_ResultPath: + ptype = "Result"; + join = false; + break; case T_NestPath: ptype = "Nestloop"; join = true; @@ -762,10 +770,15 @@ print_path(Query *root, Path *path, int indent) for (i = 0; i < indent; i++) printf("\t"); - printf("%s(", ptype); - print_relids(path->parent->relids); - printf(") rows=%.0f cost=%.2f..%.2f\n", - path->parent->rows, path->startup_cost, path->total_cost); + printf("%s", ptype); + + if (path->parent) + { + printf("("); + print_relids(path->parent->relids); + printf(") rows=%.0f", path->parent->rows); + } + printf(" cost=%.2f..%.2f\n", path->startup_cost, path->total_cost); if (path->pathkeys) { @@ -785,7 +798,7 @@ print_path(Query *root, Path *path, int indent) print_restrictclauses(root, jp->joinrestrictinfo); printf("\n"); - if (nodeTag(path) == T_MergePath) + if (IsA(path, MergePath)) { MergePath *mp = (MergePath *) path; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 9cdbcc2e5e..5a2acbd276 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.119 2002/09/18 21:35:21 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.120 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -34,6 +34,7 @@ static Scan *create_scan_plan(Query *root, Path *best_path); static Join *create_join_plan(Query *root, JoinPath *best_path); static Append *create_append_plan(Query *root, AppendPath *best_path); +static Result *create_result_plan(Query *root, ResultPath *best_path); static SeqScan *create_seqscan_plan(Path *best_path, List *tlist, List *scan_clauses); static IndexScan *create_indexscan_plan(Query *root, IndexPath *best_path, @@ -135,6 +136,10 @@ create_plan(Query *root, Path *best_path) plan = (Plan *) create_append_plan(root, (AppendPath *) best_path); break; + case T_Result: + plan = (Plan *) create_result_plan(root, + (ResultPath *) best_path); + break; default: elog(ERROR, "create_plan: unknown pathtype %d", best_path->pathtype); @@ -342,6 +347,35 @@ create_append_plan(Query *root, AppendPath *best_path) return plan; } +/* + * create_result_plan + * Create a Result plan for 'best_path' and (recursively) plans + * for its subpaths. + * + * Returns a Plan node. + */ +static Result * +create_result_plan(Query *root, ResultPath *best_path) +{ + Result *plan; + List *tlist; + Plan *subplan; + + if (best_path->path.parent) + tlist = best_path->path.parent->targetlist; + else + tlist = NIL; /* will be filled in later */ + + if (best_path->subpath) + subplan = create_plan(root, best_path->subpath); + else + subplan = NULL; + + plan = make_result(tlist, (Node *) best_path->constantqual, subplan); + + return plan; +} + /***************************************************************************** * @@ -1605,11 +1639,16 @@ make_material(List *tlist, Plan *lefttree) } Agg * -make_agg(List *tlist, List *qual, Plan *lefttree) +make_agg(List *tlist, List *qual, AggStrategy aggstrategy, + int ngrp, AttrNumber *grpColIdx, Plan *lefttree) { Agg *node = makeNode(Agg); Plan *plan = &node->plan; + node->aggstrategy = aggstrategy; + node->numCols = ngrp; + node->grpColIdx = grpColIdx; + copy_plan_costsize(plan, lefttree); /* @@ -1621,23 +1660,22 @@ make_agg(List *tlist, List *qual, Plan *lefttree) length(pull_agg_clause((Node *) qual))); /* - * We will produce a single output tuple if the input is not a Group, + * We will produce a single output tuple if not grouping, * and a tuple per group otherwise. For now, estimate the number of * groups as 10% of the number of tuples --- bogus, but how to do - * better? (Note we assume the input Group node is in "tuplePerGroup" - * mode, so it didn't reduce its row count already.) + * better? */ - if (IsA(lefttree, Group)) + if (aggstrategy == AGG_PLAIN) + { + plan->plan_rows = 1; + plan->startup_cost = plan->total_cost; + } + else { plan->plan_rows *= 0.1; if (plan->plan_rows < 1) plan->plan_rows = 1; } - else - { - plan->plan_rows = 1; - plan->startup_cost = plan->total_cost; - } plan->state = (EState *) NULL; plan->qual = qual; @@ -1650,7 +1688,6 @@ make_agg(List *tlist, List *qual, Plan *lefttree) Group * make_group(List *tlist, - bool tuplePerGroup, int ngrp, AttrNumber *grpColIdx, Plan *lefttree) @@ -1667,25 +1704,18 @@ make_group(List *tlist, plan->total_cost += cpu_operator_cost * plan->plan_rows * ngrp; /* - * If tuplePerGroup (which is named exactly backwards) is true, we - * will return all the input tuples, so the input node's row count is - * OK. Otherwise, we'll return only one tuple from each group. For - * now, estimate the number of groups as 10% of the number of tuples + * Estimate the number of groups as 10% of the number of tuples * --- bogus, but how to do better? */ - if (!tuplePerGroup) - { - plan->plan_rows *= 0.1; - if (plan->plan_rows < 1) - plan->plan_rows = 1; - } + plan->plan_rows *= 0.1; + if (plan->plan_rows < 1) + plan->plan_rows = 1; plan->state = (EState *) NULL; plan->qual = NULL; plan->targetlist = tlist; plan->lefttree = lefttree; plan->righttree = (Plan *) NULL; - node->tuplePerGroup = tuplePerGroup; node->numCols = ngrp; node->grpColIdx = grpColIdx; @@ -1883,9 +1913,6 @@ make_result(List *tlist, Result *node = makeNode(Result); Plan *plan = &node->plan; -#ifdef NOT_USED - tlist = generate_fjoin(tlist); -#endif if (subplan) copy_plan_costsize(plan, subplan); else @@ -1906,57 +1933,3 @@ make_result(List *tlist, return node; } - -#ifdef NOT_USED -List * -generate_fjoin(List *tlist) -{ - List tlistP; - List newTlist = NIL; - List fjoinList = NIL; - int nIters = 0; - - /* - * Break the target list into elements with Iter nodes, and those - * without them. - */ - foreach(tlistP, tlist) - { - List tlistElem; - - tlistElem = lfirst(tlistP); - if (IsA(lsecond(tlistElem), Iter)) - { - nIters++; - fjoinList = lappend(fjoinList, tlistElem); - } - else - newTlist = lappend(newTlist, tlistElem); - } - - /* - * if we have an Iter node then we need to flatten. - */ - if (nIters > 0) - { - List *inner; - List *tempList; - Fjoin *fjoinNode; - DatumPtr results = (DatumPtr) palloc(nIters * sizeof(Datum)); - BoolPtr alwaysDone = (BoolPtr) palloc(nIters * sizeof(bool)); - - inner = lfirst(fjoinList); - fjoinList = lnext(fjoinList); - fjoinNode = (Fjoin) MakeFjoin(false, - nIters, - inner, - results, - alwaysDone); - tempList = lcons(fjoinNode, fjoinList); - newTlist = lappend(newTlist, tempList); - } - return newTlist; - return tlist; /* do nothing for now - ay 10/94 */ -} - -#endif diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 41f914b8f9..4354a5eb03 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -14,15 +14,13 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.70 2002/09/02 02:47:02 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.71 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" - #include "optimizer/clauses.h" -#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" @@ -31,31 +29,37 @@ #include "utils/memutils.h" -static Plan *subplanner(Query *root, List *flat_tlist, - double tuple_fraction); - - /*-------------------- * query_planner - * Generate a plan for a basic query, which may involve joins but - * not any fancier features. + * Generate a path (that is, a simplified plan) for a basic query, + * which may involve joins but not any fancier features. * + * Since query_planner does not handle the toplevel processing (grouping, + * sorting, etc) it cannot select the best path by itself. It selects + * two paths: the cheapest path that produces the required tuples, independent + * of any ordering considerations, and the cheapest path that produces the + * required tuples in the required ordering, if there is a path that + * can produce them without an explicit top-level sort step. The caller + * (grouping_planner) will make the final decision about which to use. + * + * Input parameters: + * root is the query to plan * tlist is the target list the query should produce (NOT root->targetList!) * tuple_fraction is the fraction of tuples we expect will be retrieved * - * Note: the Query node now also includes a query_pathkeys field, which - * is both an input and an output of query_planner(). The input value - * signals query_planner that the indicated sort order is wanted in the - * final output plan. The output value is the actual pathkeys of the - * selected path. This might not be the same as what the caller requested; - * the caller must do pathkeys_contained_in() to decide whether an - * explicit sort is still needed. (The main reason query_pathkeys is a - * Query field and not a passed parameter is that the low-level routines - * in indxpath.c need to see it.) The pathkeys value passed to query_planner - * has not yet been "canonicalized", since the necessary info does not get - * computed until subplanner() scans the qual clauses. We canonicalize it - * inside subplanner() as soon as that task is done. The output value - * will be in canonical form as well. + * Output parameters: + * *cheapest_path receives the overall-cheapest path for the query + * *sorted_path receives the cheapest presorted path for the query, + * if any (it may be NULL, or the same as cheapest_path) + * + * Note: the Query node also includes a query_pathkeys field, which is both + * an input and an output of query_planner(). The input value signals + * query_planner that the indicated sort order is wanted in the final output + * plan. But this value has not yet been "canonicalized", since the needed + * info does not get computed until we scan the qual clauses. We canonicalize + * it as soon as that task is done. (The main reason query_pathkeys is a + * Query field and not a passed parameter is that the low-level routines in + * indxpath.c need to see it.) * * tuple_fraction is interpreted as follows: * 0 (or less): expect all tuples to be retrieved (normal case) @@ -66,18 +70,14 @@ static Plan *subplanner(Query *root, List *flat_tlist, * Note that while this routine and its subroutines treat a negative * tuple_fraction the same as 0, grouping_planner has a different * interpretation. - * - * Returns a query plan. *-------------------- */ -Plan * -query_planner(Query *root, - List *tlist, - double tuple_fraction) +void +query_planner(Query *root, List *tlist, double tuple_fraction, + Path **cheapest_path, Path **sorted_path) { List *constant_quals; - List *var_only_tlist; - Plan *subplan; + RelOptInfo *final_rel; /* * If the query has an empty join tree, then it's something easy like @@ -85,11 +85,10 @@ query_planner(Query *root, */ if (root->jointree->fromlist == NIL) { - root->query_pathkeys = NIL; /* signal unordered result */ - - /* Make childless Result node to evaluate given tlist. */ - return (Plan *) make_result(tlist, root->jointree->quals, - (Plan *) NULL); + *cheapest_path = (Path *) create_result_path(NULL, NULL, + (List *) root->jointree->quals); + *sorted_path = NULL; + return; } /* @@ -107,80 +106,8 @@ query_planner(Query *root, &constant_quals); /* - * Create a target list that consists solely of (resdom var) target - * list entries, i.e., contains no arbitrary expressions. - * - * All subplan nodes will have "flat" (var-only) tlists. - * - * This implies that all expression evaluations are done at the root of - * the plan tree. Once upon a time there was code to try to push - * expensive function calls down to lower plan nodes, but that's dead - * code and has been for a long time... + * init planner lists to empty */ - var_only_tlist = flatten_tlist(tlist); - - /* - * Choose the best access path and build a plan for it. - */ - subplan = subplanner(root, var_only_tlist, tuple_fraction); - - /* - * Build a result node to control the plan if we have constant quals, - * or if the top-level plan node is one that cannot do expression - * evaluation (it won't be able to evaluate the requested tlist). - * Currently, the only plan node we might see here that falls into - * that category is Append. - * - * XXX future improvement: if the given tlist is flat anyway, we don't - * really need a Result node. - */ - if (constant_quals || IsA(subplan, Append)) - { - /* - * The result node will also be responsible for evaluating the - * originally requested tlist. - */ - subplan = (Plan *) make_result(tlist, - (Node *) constant_quals, - subplan); - } - else - { - /* - * Replace the toplevel plan node's flattened target list with the - * targetlist given by my caller, so that expressions are - * evaluated. - */ - subplan->targetlist = tlist; - } - - return subplan; -} - -/* - * subplanner - * - * Subplanner creates an entire plan consisting of joins and scans - * for processing a single level of attributes. - * - * flat_tlist is the flattened target list - * tuple_fraction is the fraction of tuples we expect will be retrieved - * - * See query_planner() comments about the interpretation of tuple_fraction. - * - * Returns a subplan. - */ -static Plan * -subplanner(Query *root, - List *flat_tlist, - double tuple_fraction) -{ - RelOptInfo *final_rel; - Plan *resultplan; - Path *cheapestpath; - Path *presortedpath; - - /* init lists to empty */ root->base_rel_list = NIL; root->other_rel_list = NIL; root->join_rel_list = NIL; @@ -197,8 +124,14 @@ subplanner(Query *root, * clauses are added to appropriate lists belonging to the mentioned * relations. We also build lists of equijoined keys for pathkey * construction. + * + * Note: all subplan nodes will have "flat" (var-only) tlists. + * This implies that all expression evaluations are done at the root of + * the plan tree. Once upon a time there was code to try to push + * expensive function calls down to lower plan nodes, but that's dead + * code and has been for a long time... */ - build_base_rel_tlists(root, flat_tlist); + build_base_rel_tlists(root, tlist); (void) distribute_quals_to_rels(root, (Node *) root->jointree); @@ -220,31 +153,8 @@ subplanner(Query *root, */ final_rel = make_one_rel(root); - if (!final_rel) - elog(ERROR, "subplanner: failed to construct a relation"); - -#ifdef NOT_USED /* fix xfunc */ - - /* - * Perform Predicate Migration on each path, to optimize and correctly - * assess the cost of each before choosing the cheapest one. -- JMH, - * 11/16/92 - * - * Needn't do so if the top rel is pruneable: that means there's no - * expensive functions left to pull up. -- JMH, 11/22/92 - */ - if (XfuncMode != XFUNC_OFF && XfuncMode != XFUNC_NOPM && - XfuncMode != XFUNC_NOPULL && !final_rel->pruneable) - { - List *pathnode; - - foreach(pathnode, final_rel->pathlist) - { - if (xfunc_do_predmig((Path *) lfirst(pathnode))) - set_cheapest(final_rel); - } - } -#endif + if (!final_rel || !final_rel->cheapest_total_path) + elog(ERROR, "query_planner: failed to construct a relation"); /* * Now that we have an estimate of the final rel's size, we can @@ -255,75 +165,35 @@ subplanner(Query *root, tuple_fraction /= final_rel->rows; /* - * Determine the cheapest path, independently of any ordering - * considerations. We do, however, take into account whether the - * whole plan is expected to be evaluated or not. + * Pick out the cheapest-total path and the cheapest presorted path + * for the requested pathkeys (if there is one). We can take the + * tuple fraction into account when selecting the cheapest presorted + * path, but not when selecting the cheapest-total path, since if we + * have to sort then we'll have to fetch all the tuples. (But there's + * a special case: if query_pathkeys is NIL, meaning order doesn't + * matter, then the "cheapest presorted" path will be the cheapest + * overall for the tuple fraction.) */ - if (tuple_fraction <= 0.0 || tuple_fraction >= 1.0) - cheapestpath = final_rel->cheapest_total_path; - else - cheapestpath = - get_cheapest_fractional_path_for_pathkeys(final_rel->pathlist, - NIL, - tuple_fraction); + *cheapest_path = final_rel->cheapest_total_path; - Assert(cheapestpath != NULL); - - /* - * Select the best path and create a subplan to execute it. - * - * If no special sort order is wanted, or if the cheapest path is already - * appropriately ordered, we use the cheapest path found above. - */ - if (root->query_pathkeys == NIL || - pathkeys_contained_in(root->query_pathkeys, - cheapestpath->pathkeys)) - { - root->query_pathkeys = cheapestpath->pathkeys; - resultplan = create_plan(root, cheapestpath); - goto plan_built; - } - - /* - * Otherwise, look to see if we have an already-ordered path that is - * cheaper than doing an explicit sort on the cheapest-total-cost - * path. - */ - cheapestpath = final_rel->cheapest_total_path; - presortedpath = + *sorted_path = get_cheapest_fractional_path_for_pathkeys(final_rel->pathlist, root->query_pathkeys, tuple_fraction); - if (presortedpath) - { - Path sort_path; /* dummy for result of cost_sort */ - - cost_sort(&sort_path, root, root->query_pathkeys, - final_rel->rows, final_rel->width); - sort_path.startup_cost += cheapestpath->total_cost; - sort_path.total_cost += cheapestpath->total_cost; - if (compare_fractional_path_costs(presortedpath, &sort_path, - tuple_fraction) <= 0) - { - /* Presorted path is cheaper, use it */ - root->query_pathkeys = presortedpath->pathkeys; - resultplan = create_plan(root, presortedpath); - goto plan_built; - } - /* otherwise, doing it the hard way is still cheaper */ - } /* - * Nothing for it but to sort the cheapest-total-cost path --- but we - * let the caller do that. grouping_planner has to be able to add a - * sort node anyway, so no need for extra code here. (Furthermore, - * the given pathkeys might involve something we can't compute here, - * such as an aggregate function...) + * If we have constant quals, add a toplevel Result step to process them. */ - root->query_pathkeys = cheapestpath->pathkeys; - resultplan = create_plan(root, cheapestpath); - -plan_built: - - return resultplan; + if (constant_quals) + { + *cheapest_path = (Path *) + create_result_path((*cheapest_path)->parent, + *cheapest_path, + constant_quals); + if (*sorted_path) + *sorted_path = (Path *) + create_result_path((*sorted_path)->parent, + *sorted_path, + constant_quals); + } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b607173a4c..cc8e7a698d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.125 2002/09/24 18:38:23 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.126 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,6 +21,8 @@ #include "nodes/print.h" #endif #include "optimizer/clauses.h" +#include "optimizer/cost.h" +#include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" @@ -53,10 +55,10 @@ static Plan *inheritance_planner(Query *parse, List *inheritlist); static Plan *grouping_planner(Query *parse, double tuple_fraction); static List *make_subplanTargetList(Query *parse, List *tlist, AttrNumber **groupColIdx); -static Plan *make_groupplan(Query *parse, - List *group_tlist, bool tuplePerGroup, - List *groupClause, AttrNumber *grpColIdx, - bool is_presorted, Plan *subplan); +static Plan *make_groupsortplan(Query *parse, + List *groupClause, + AttrNumber *grpColIdx, + Plan *subplan); static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist); @@ -877,9 +879,7 @@ grouping_planner(Query *parse, double tuple_fraction) List *tlist = parse->targetList; Plan *result_plan; List *current_pathkeys; - List *group_pathkeys; List *sort_pathkeys; - AttrNumber *groupColIdx = NULL; if (parse->setOperations) { @@ -917,17 +917,20 @@ grouping_planner(Query *parse, double tuple_fraction) current_pathkeys = NIL; /* - * Calculate pathkeys that represent grouping/ordering - * requirements (grouping should always be null, but...) + * Calculate pathkeys that represent ordering requirements */ - group_pathkeys = make_pathkeys_for_sortclauses(parse->groupClause, - tlist); sort_pathkeys = make_pathkeys_for_sortclauses(parse->sortClause, tlist); + sort_pathkeys = canonicalize_pathkeys(parse, sort_pathkeys); } else { + /* No set operations, do regular planning */ List *sub_tlist; + List *group_pathkeys; + AttrNumber *groupColIdx = NULL; + Path *cheapest_path; + Path *sorted_path; /* Preprocess targetlist in case we are inside an INSERT/UPDATE. */ tlist = preprocess_targetlist(tlist, @@ -1192,99 +1195,162 @@ grouping_planner(Query *parse, double tuple_fraction) tuple_fraction = 0.25; } - /* Generate the basic plan for this Query */ - result_plan = query_planner(parse, - sub_tlist, - tuple_fraction); + /* + * Generate the best unsorted and presorted paths for this Query + * (but note there may not be any presorted path). + */ + query_planner(parse, sub_tlist, tuple_fraction, + &cheapest_path, &sorted_path); /* - * query_planner returns actual sort order (which is not - * necessarily what we requested) in query_pathkeys. + * We couldn't canonicalize group_pathkeys and sort_pathkeys before + * running query_planner(), so do it now. */ - current_pathkeys = parse->query_pathkeys; - } - - /* - * We couldn't canonicalize group_pathkeys and sort_pathkeys before - * running query_planner(), so do it now. - */ - group_pathkeys = canonicalize_pathkeys(parse, group_pathkeys); - sort_pathkeys = canonicalize_pathkeys(parse, sort_pathkeys); - - /* - * If we have a GROUP BY clause, insert a group node (plus the - * appropriate sort node, if necessary). - */ - if (parse->groupClause) - { - bool tuplePerGroup; - List *group_tlist; - bool is_sorted; + group_pathkeys = canonicalize_pathkeys(parse, group_pathkeys); + sort_pathkeys = canonicalize_pathkeys(parse, sort_pathkeys); /* - * Decide whether how many tuples per group the Group node needs - * to return. (Needs only one tuple per group if no aggregate is - * present. Otherwise, need every tuple from the group to do the - * aggregation.) Note tuplePerGroup is named backwards :-( + * Select the best path and create a plan to execute it. + * + * If no special sort order is wanted, or if the cheapest path is + * already appropriately ordered, use the cheapest path. + * Otherwise, look to see if we have an already-ordered path that is + * cheaper than doing an explicit sort on the cheapest-total-cost + * path. */ - tuplePerGroup = parse->hasAggs; - - /* - * If there are aggregates then the Group node should just return - * the same set of vars as the subplan did. If there are no aggs - * then the Group node had better compute the final tlist. - */ - if (parse->hasAggs) - group_tlist = new_unsorted_tlist(result_plan->targetlist); - else - group_tlist = tlist; - - /* - * Figure out whether the path result is already ordered the way - * we need it --- if so, no need for an explicit sort step. - */ - if (pathkeys_contained_in(group_pathkeys, current_pathkeys)) + if (parse->query_pathkeys == NIL || + pathkeys_contained_in(parse->query_pathkeys, + cheapest_path->pathkeys)) { - is_sorted = true; /* no sort needed now */ - /* current_pathkeys remains unchanged */ + result_plan = create_plan(parse, cheapest_path); + current_pathkeys = cheapest_path->pathkeys; + } + else if (sorted_path) + { + Path sort_path; /* dummy for result of cost_sort */ + + cost_sort(&sort_path, parse, parse->query_pathkeys, + sorted_path->parent->rows, sorted_path->parent->width); + sort_path.startup_cost += cheapest_path->total_cost; + sort_path.total_cost += cheapest_path->total_cost; + if (compare_fractional_path_costs(sorted_path, &sort_path, + tuple_fraction) <= 0) + { + /* Presorted path is cheaper, use it */ + result_plan = create_plan(parse, sorted_path); + current_pathkeys = sorted_path->pathkeys; + } + else + { + /* otherwise, doing it the hard way is still cheaper */ + result_plan = create_plan(parse, cheapest_path); + current_pathkeys = cheapest_path->pathkeys; + } } else { /* - * We will need to do an explicit sort by the GROUP BY clause. - * make_groupplan will do the work, but set current_pathkeys - * to indicate the resulting order. + * No sorted path, so we must use the cheapest-total path. + * The actual sort step will be generated below. */ - is_sorted = false; - current_pathkeys = group_pathkeys; + result_plan = create_plan(parse, cheapest_path); + current_pathkeys = cheapest_path->pathkeys; } - result_plan = make_groupplan(parse, - group_tlist, - tuplePerGroup, - parse->groupClause, - groupColIdx, - is_sorted, - result_plan); - } + /* + * create_plan() returns a plan with just a "flat" tlist of required + * Vars. We want to insert the sub_tlist as the tlist of the top + * plan node. If the top-level plan node is one that cannot do + * expression evaluation, we must insert a Result node to project the + * desired tlist. + * Currently, the only plan node we might see here that falls into + * that category is Append. + */ + if (IsA(result_plan, Append)) + { + result_plan = (Plan *) make_result(sub_tlist, NULL, result_plan); + } + else + { + /* + * Otherwise, just replace the flat tlist with the desired tlist. + */ + result_plan->targetlist = sub_tlist; + } - /* - * If aggregate is present, insert the Agg node - * - * HAVING clause, if any, becomes qual of the Agg node - */ - if (parse->hasAggs) - { - result_plan = (Plan *) make_agg(tlist, - (List *) parse->havingQual, - result_plan); - /* Note: Agg does not affect any existing sort order of the tuples */ - } - else - { - /* If there are no Aggs, we shouldn't have any HAVING qual anymore */ - Assert(parse->havingQual == NULL); - } + /* + * If any aggregate is present, insert the Agg node, plus an explicit + * sort if necessary. + * + * HAVING clause, if any, becomes qual of the Agg node + */ + if (parse->hasAggs) + { + AggStrategy aggstrategy; + + if (parse->groupClause) + { + aggstrategy = AGG_SORTED; + /* + * Add an explicit sort if we couldn't make the path come out + * the way the AGG node needs it. + */ + if (!pathkeys_contained_in(group_pathkeys, current_pathkeys)) + { + result_plan = make_groupsortplan(parse, + parse->groupClause, + groupColIdx, + result_plan); + current_pathkeys = group_pathkeys; + } + } + else + aggstrategy = AGG_PLAIN; + + result_plan = (Plan *) make_agg(tlist, + (List *) parse->havingQual, + aggstrategy, + length(parse->groupClause), + groupColIdx, + result_plan); + /* + * Note: plain or grouped Agg does not affect any existing + * sort order of the tuples + */ + } + else + { + /* + * If there are no Aggs, we shouldn't have any HAVING qual anymore + */ + Assert(parse->havingQual == NULL); + + /* + * If we have a GROUP BY clause, insert a group node (plus the + * appropriate sort node, if necessary). + */ + if (parse->groupClause) + { + /* + * Add an explicit sort if we couldn't make the path come out + * the way the GROUP node needs it. + */ + if (!pathkeys_contained_in(group_pathkeys, current_pathkeys)) + { + result_plan = make_groupsortplan(parse, + parse->groupClause, + groupColIdx, + result_plan); + current_pathkeys = group_pathkeys; + } + + result_plan = (Plan *) make_group(tlist, + length(parse->groupClause), + groupColIdx, + result_plan); + } + } + } /* end of if (setOperations) */ /* * If we were not able to make the plan come out in the right order, @@ -1323,7 +1389,7 @@ grouping_planner(Query *parse, double tuple_fraction) * make_subplanTargetList * Generate appropriate target list when grouping is required. * - * When grouping_planner inserts Aggregate and/or Group plan nodes above + * When grouping_planner inserts Aggregate or Group plan nodes above * the result of query_planner, we typically want to pass a different * target list to query_planner than the outer plan nodes should have. * This routine generates the correct target list for the subplan. @@ -1433,62 +1499,48 @@ make_subplanTargetList(Query *parse, } /* - * make_groupplan - * Add a Group node for GROUP BY processing. - * If we couldn't make the subplan produce presorted output for grouping, - * first add an explicit Sort node. + * make_groupsortplan + * Add a Sort node to explicitly sort according to the GROUP BY clause. + * + * Note: the Sort node always just takes a copy of the subplan's tlist + * plus ordering information. (This might seem inefficient if the + * subplan contains complex GROUP BY expressions, but in fact Sort + * does not evaluate its targetlist --- it only outputs the same + * tuples in a new order. So the expressions we might be copying + * are just dummies with no extra execution cost.) */ static Plan * -make_groupplan(Query *parse, - List *group_tlist, - bool tuplePerGroup, - List *groupClause, - AttrNumber *grpColIdx, - bool is_presorted, - Plan *subplan) +make_groupsortplan(Query *parse, + List *groupClause, + AttrNumber *grpColIdx, + Plan *subplan) { - int numCols = length(groupClause); + List *sort_tlist = new_unsorted_tlist(subplan->targetlist); + int keyno = 0; + List *gl; - if (!is_presorted) + foreach(gl, groupClause) { + GroupClause *grpcl = (GroupClause *) lfirst(gl); + TargetEntry *te = nth(grpColIdx[keyno] - 1, sort_tlist); + Resdom *resdom = te->resdom; + /* - * The Sort node always just takes a copy of the subplan's tlist - * plus ordering information. (This might seem inefficient if the - * subplan contains complex GROUP BY expressions, but in fact Sort - * does not evaluate its targetlist --- it only outputs the same - * tuples in a new order. So the expressions we might be copying - * are just dummies with no extra execution cost.) + * Check for the possibility of duplicate group-by clauses --- + * the parser should have removed 'em, but the Sort executor + * will get terribly confused if any get through! */ - List *sort_tlist = new_unsorted_tlist(subplan->targetlist); - int keyno = 0; - List *gl; - - foreach(gl, groupClause) + if (resdom->reskey == 0) { - GroupClause *grpcl = (GroupClause *) lfirst(gl); - TargetEntry *te = nth(grpColIdx[keyno] - 1, sort_tlist); - Resdom *resdom = te->resdom; - - /* - * Check for the possibility of duplicate group-by clauses --- - * the parser should have removed 'em, but the Sort executor - * will get terribly confused if any get through! - */ - if (resdom->reskey == 0) - { - /* OK, insert the ordering info needed by the executor. */ - resdom->reskey = ++keyno; - resdom->reskeyop = grpcl->sortop; - } + /* OK, insert the ordering info needed by the executor. */ + resdom->reskey = ++keyno; + resdom->reskeyop = grpcl->sortop; } - - Assert(keyno > 0); - - subplan = (Plan *) make_sort(parse, sort_tlist, subplan, keyno); } - return (Plan *) make_group(group_tlist, tuplePerGroup, numCols, - grpColIdx, subplan); + Assert(keyno > 0); + + return (Plan *) make_sort(parse, sort_tlist, subplan, keyno); } /* diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 4b3c9809b8..7dd0dce689 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.78 2002/06/20 20:29:31 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.79 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -405,7 +405,6 @@ create_tidscan_path(Query *root, RelOptInfo *rel, List *tideval) * create_append_path * Creates a path corresponding to an Append plan, returning the * pathnode. - * */ AppendPath * create_append_path(RelOptInfo *rel, List *subpaths) @@ -433,6 +432,41 @@ create_append_path(RelOptInfo *rel, List *subpaths) return pathnode; } +/* + * create_result_path + * Creates a path corresponding to a Result plan, returning the + * pathnode. + */ +ResultPath * +create_result_path(RelOptInfo *rel, Path *subpath, List *constantqual) +{ + ResultPath *pathnode = makeNode(ResultPath); + + pathnode->path.pathtype = T_Result; + pathnode->path.parent = rel; /* may be NULL */ + + if (subpath) + pathnode->path.pathkeys = subpath->pathkeys; + else + pathnode->path.pathkeys = NIL; + + pathnode->subpath = subpath; + pathnode->constantqual = constantqual; + + if (subpath) + { + pathnode->path.startup_cost = subpath->startup_cost; + pathnode->path.total_cost = subpath->total_cost; + } + else + { + pathnode->path.startup_cost = 0; + pathnode->path.total_cost = cpu_tuple_cost; + } + + return pathnode; +} + /* * create_subqueryscan_path * Creates a path corresponding to a sequential scan of a subquery, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index facef90894..533d296186 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: execnodes.h,v 1.75 2002/09/04 20:31:42 momjian Exp $ + * $Id: execnodes.h,v 1.76 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -673,6 +673,8 @@ typedef struct AggState CommonScanState csstate; /* its first field is NodeTag */ List *aggs; /* all Aggref nodes in targetlist & quals */ int numaggs; /* length of list (could be zero!) */ + FmgrInfo *eqfunctions; /* per-grouping-field equality fns */ + HeapTuple grp_firstTuple; /* copy of first tuple of current group */ AggStatePerAgg peragg; /* per-Aggref working state */ MemoryContext tup_cxt; /* context for per-output-tuple * expressions */ @@ -691,7 +693,7 @@ typedef struct GroupState FmgrInfo *eqfunctions; /* per-field lookup data for equality fns */ bool grp_useFirstTuple; /* first tuple not processed yet */ bool grp_done; - HeapTuple grp_firstTuple; + HeapTuple grp_firstTuple; /* copy of first tuple of current group */ } GroupState; /* ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 5320c1b10d..aad54a9c26 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.120 2002/10/11 04:16:44 momjian Exp $ + * $Id: nodes.h,v 1.121 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -82,6 +82,7 @@ typedef enum NodeTag T_HashPath, T_TidPath, T_AppendPath, + T_ResultPath, T_PathKeyItem, T_RestrictInfo, T_JoinInfo, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ee0d221013..8303fe9d09 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.209 2002/10/14 22:14:35 tgl Exp $ + * $Id: parsenodes.h,v 1.210 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -101,7 +101,7 @@ typedef struct Query List *join_rel_list; /* list of join-relation RelOptInfos */ List *equi_key_list; /* list of lists of equijoined * PathKeyItems */ - List *query_pathkeys; /* pathkeys for query_planner()'s result */ + List *query_pathkeys; /* desired pathkeys for query_planner() */ } Query; diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 8d26fea6f0..63c8f20d80 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: plannodes.h,v 1.58 2002/09/04 20:31:44 momjian Exp $ + * $Id: plannodes.h,v 1.59 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -140,17 +140,23 @@ typedef struct Plan * =============== */ -/* all plan nodes "derive" from the Plan structure by having the - Plan structure as the first field. This ensures that everything works - when nodes are cast to Plan's. (node pointers are frequently cast to Plan* - when passed around generically in the executor */ +/* + * all plan nodes "derive" from the Plan structure by having the + * Plan structure as the first field. This ensures that everything works + * when nodes are cast to Plan's. (node pointers are frequently cast to Plan* + * when passed around generically in the executor) + */ /* ---------------- * Result node - * If no outer plan, evaluate a variable-free targetlist. - * If outer plan, return tuples from outer plan that satisfy - * given quals (we can also do a level of projection) + * If outer plan, return tuples from outer plan (after a level of + * projection as shown by targetlist). + * + * If resconstantqual isn't NULL, it represents a one-time qualification + * test (i.e., one that doesn't depend on any variables from the outer plan, + * so needs to be evaluated only once). * ---------------- */ typedef struct Result @@ -318,30 +324,45 @@ typedef struct HashJoin /* --------------- * aggregate node + * + * An Agg node implements plain or grouped aggregation. For grouped + * aggregation, we can work with presorted input or unsorted input; + * the latter strategy uses an internal hashtable. + * + * Notice the lack of any direct info about the aggregate functions to be + * computed. They are found by scanning the node's tlist and quals during + * executor startup. (It is possible that there are no aggregate functions; + * this could happen if they get optimized away by constant-folding, or if + * we are using the Agg node to implement hash-based grouping.) * --------------- */ +typedef enum AggStrategy +{ + AGG_PLAIN, /* simple agg across all input rows */ + AGG_SORTED, /* grouped agg, input must be sorted */ + AGG_HASHED /* grouped agg, use internal hashtable */ +} AggStrategy; + typedef struct Agg { Plan plan; + AggStrategy aggstrategy; + int numCols; /* number of grouping columns */ + AttrNumber *grpColIdx; /* their indexes in the target list */ AggState *aggstate; } Agg; /* --------------- * group node - - * use for queries with GROUP BY specified. - * - * If tuplePerGroup is true, one tuple (with group columns only) is - * returned for each group and NULL is returned when there are no more - * groups. Otherwise, all the tuples of a group are returned with a - * NULL returned at the end of each group. (see nodeGroup.c for details) + * Used for queries with GROUP BY (but no aggregates) specified. + * The input must be presorted according to the grouping columns. * --------------- */ typedef struct Group { Plan plan; - bool tuplePerGroup; /* what tuples to return (see above) */ - int numCols; /* number of group columns */ - AttrNumber *grpColIdx; /* indexes into the target list */ + int numCols; /* number of grouping columns */ + AttrNumber *grpColIdx; /* their indexes in the target list */ GroupState *grpstate; } Group; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 071addbc1b..480fd9630b 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1,13 +1,13 @@ /*------------------------------------------------------------------------- * * relation.h - * Definitions for internal planner nodes. + * Definitions for planner's internal data structures. * * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: relation.h,v 1.67 2002/09/04 20:31:44 momjian Exp $ + * $Id: relation.h,v 1.68 2002/11/06 00:00:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -402,6 +402,19 @@ typedef struct AppendPath List *subpaths; /* list of component Paths */ } AppendPath; +/* + * ResultPath represents use of a Result plan node, either to compute a + * variable-free targetlist or to gate execution of a subplan with a + * one-time (variable-free) qual condition. Note that in the former case + * path.parent will be NULL; in the latter case it is copied from the subpath. + */ +typedef struct ResultPath +{ + Path path; + Path *subpath; + List *constantqual; +} ResultPath; + /* * All join-type paths share these fields. */ diff --git a/src/include/optimizer/geqo_misc.h b/src/include/optimizer/geqo_misc.h index 6599bf7697..5c276c0fb2 100644 --- a/src/include/optimizer/geqo_misc.h +++ b/src/include/optimizer/geqo_misc.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: geqo_misc.h,v 1.21 2002/09/04 20:31:45 momjian Exp $ + * $Id: geqo_misc.h,v 1.22 2002/11/06 00:00:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,9 +32,6 @@ extern void print_pool(FILE *fp, Pool *pool, int start, int stop); extern void print_gen(FILE *fp, Pool *pool, int generation); extern void print_edge_table(FILE *fp, Edge *edge_table, int num_gene); -extern void geqo_print_rel(Query *root, RelOptInfo *rel); -extern void geqo_print_path(Query *root, Path *path, int indent); -extern void geqo_print_joinclauses(Query *root, List *clauses); #endif /* GEQO_DEBUG */ #endif /* GEQO_MISC_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 0c9ce6c95e..61c433e12d 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pathnode.h,v 1.44 2002/06/20 20:29:51 momjian Exp $ + * $Id: pathnode.h,v 1.45 2002/11/06 00:00:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,6 +35,8 @@ extern IndexPath *create_index_path(Query *root, RelOptInfo *rel, extern TidPath *create_tidscan_path(Query *root, RelOptInfo *rel, List *tideval); extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths); +extern ResultPath *create_result_path(RelOptInfo *rel, Path *subpath, + List *constantqual); extern Path *create_subqueryscan_path(RelOptInfo *rel); extern Path *create_functionscan_path(Query *root, RelOptInfo *rel); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index d31cee5ddc..c927d54074 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: planmain.h,v 1.60 2002/09/04 20:31:45 momjian Exp $ + * $Id: planmain.h,v 1.61 2002/11/06 00:00:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,7 +20,8 @@ /* * prototypes for plan/planmain.c */ -extern Plan *query_planner(Query *root, List *tlist, double tuple_fraction); +extern void query_planner(Query *root, List *tlist, double tuple_fraction, + Path **cheapest_path, Path **sorted_path); /* * prototypes for plan/createplan.c @@ -33,9 +34,10 @@ extern Sort *make_sort(Query *root, List *tlist, Plan *lefttree, int keycount); extern Sort *make_sort_from_pathkeys(Query *root, List *tlist, Plan *lefttree, List *pathkeys); -extern Agg *make_agg(List *tlist, List *qual, Plan *lefttree); -extern Group *make_group(List *tlist, bool tuplePerGroup, int ngrp, - AttrNumber *grpColIdx, Plan *lefttree); +extern Agg *make_agg(List *tlist, List *qual, AggStrategy aggstrategy, + int ngrp, AttrNumber *grpColIdx, Plan *lefttree); +extern Group *make_group(List *tlist, int ngrp, AttrNumber *grpColIdx, + Plan *lefttree); extern Material *make_material(List *tlist, Plan *lefttree); extern Unique *make_unique(List *tlist, Plan *lefttree, List *distinctList); extern Limit *make_limit(List *tlist, Plan *lefttree,