Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Postgres/src/backend/optimizer/plan/   (Postgres Database Version 18.4©)  Datei vom 11.4.2026 mit Größe 16 kB image not shown  

Quellcode-Bibliothek planagg.c

  Sprache: C
 

/*-------------------------------------------------------------------------
 *
 * planagg.c
 *   Special planning for aggregate queries.
 *
 * This module tries to replace MIN/MAX aggregate functions by subqueries
 * of the form
 *  (SELECT col FROM tab
 *   WHERE col IS NOT NULL AND existing-quals
 *   ORDER BY col ASC/DESC
 *   LIMIT 1)
 * Given a suitable index on tab.col, this can be much faster than the
 * generic scan-all-the-rows aggregation plan.  We can handle multiple
 * MIN/MAX aggregates by generating multiple subqueries, and their
 * orderings can be different.  However, if the query contains any
 * non-optimizable aggregates, there's no point since we'll have to
 * scan all the rows anyway.
 *
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *   src/backend/optimizer/plan/planagg.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"

static bool can_minmax_aggs(PlannerInfo *root, List **context);
static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
         Oid eqop, Oid sortop, bool reverse_sort,
         bool nulls_first);
static void minmax_qp_callback(PlannerInfo *root, void *extra);
static Oid fetch_agg_sort_op(Oid aggfnoid);


/*
 * preprocess_minmax_aggregates - preprocess MIN/MAX aggregates
 *
 * Check to see whether the query contains MIN/MAX aggregate functions that
 * might be optimizable via indexscans.  If it does, and all the aggregates
 * are potentially optimizable, then create a MinMaxAggPath and add it to
 * the (UPPERREL_GROUP_AGG, NULL) upperrel.
 *
 * This should be called by grouping_planner() just before it's ready to call
 * query_planner(), because we generate indexscan paths by cloning the
 * planner's state and invoking query_planner() on a modified version of
 * the query parsetree.  Thus, all preprocessing needed before query_planner()
 * must already be done.  This relies on the list of aggregates in
 * root->agginfos, so preprocess_aggrefs() must have been called already, too.
 */

void
preprocess_minmax_aggregates(PlannerInfo *root)
{
 Query    *parse = root->parse;
 FromExpr   *jtnode;
 RangeTblRef *rtr;
 RangeTblEntry *rte;
 List    *aggs_list;
 RelOptInfo *grouped_rel;
 ListCell   *lc;

 /* minmax_aggs list should be empty at this point */
 Assert(root->minmax_aggs == NIL);

 /* Nothing to do if query has no aggregates */
 if (!parse->hasAggs)
  return;

 Assert(!parse->setOperations); /* shouldn't get here if a setop */
 Assert(parse->rowMarks == NIL); /* nor if FOR UPDATE */

 /*
  * Reject unoptimizable cases.
  *
  * We don't handle GROUP BY or windowing, because our current
  * implementations of grouping require looking at all the rows anyway, and
  * so there's not much point in optimizing MIN/MAX.
 */

 if (parse->groupClause || list_length(parse->groupingSets) > 1 ||
  parse->hasWindowFuncs)
  return;

 /*
  * Reject if query contains any CTEs; there's no way to build an indexscan
  * on one so we couldn't succeed here.  (If the CTEs are unreferenced,
  * that's not true, but it doesn't seem worth expending cycles to check.)
 */

 if (parse->cteList)
  return;

 /*
  * We also restrict the query to reference exactly one table, since join
  * conditions can't be handled reasonably.  (We could perhaps handle a
  * query containing cartesian-product joins, but it hardly seems worth the
  * trouble.)  However, the single table could be buried in several levels
  * of FromExpr due to subqueries.  Note the "single" table could be an
  * inheritance parent, too, including the case of a UNION ALL subquery
  * that's been flattened to an appendrel.
 */

 jtnode = parse->jointree;
 while (IsA(jtnode, FromExpr))
 {
  if (list_length(jtnode->fromlist) != 1)
   return;
  jtnode = linitial(jtnode->fromlist);
 }
 if (!IsA(jtnode, RangeTblRef))
  return;
 rtr = (RangeTblRef *) jtnode;
 rte = planner_rt_fetch(rtr->rtindex, root);
 if (rte->rtekind == RTE_RELATION)
   /* ordinary relation, ok */ ;
 else if (rte->rtekind == RTE_SUBQUERY && rte->inh)
   /* flattened UNION ALL subquery, ok */ ;
 else
  return;

 /*
  * Examine all the aggregates and verify all are MIN/MAX aggregates.  Stop
  * as soon as we find one that isn't.
 */

 aggs_list = NIL;
 if (!can_minmax_aggs(root, &aggs_list))
  return;

 /*
  * OK, there is at least the possibility of performing the optimization.
  * Build an access path for each aggregate.  If any of the aggregates
  * prove to be non-indexable, give up; there is no point in optimizing
  * just some of them.
 */

 foreach(lc, aggs_list)
 {
  MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
  Oid   eqop;
  bool  reverse;

  /*
   * We'll need the equality operator that goes with the aggregate's
   * ordering operator.
 */

  eqop = get_equality_op_for_ordering_op(mminfo->aggsortop, &reverse);
  if (!OidIsValid(eqop)) /* shouldn't happen */
   elog(ERROR, "could not find equality operator for ordering operator %u",
     mminfo->aggsortop);

  /*
   * We can use either an ordering that gives NULLS FIRST or one that
   * gives NULLS LAST; furthermore there's unlikely to be much
   * performance difference between them, so it doesn't seem worth
   * costing out both ways if we get a hit on the first one.  NULLS
   * FIRST is more likely to be available if the operator is a
   * reverse-sort operator, so try that first if reverse.
 */

  if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, reverse, reverse))
   continue;
  if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, reverse, !reverse))
   continue;

  /* No indexable path for this aggregate, so fail */
  return;
 }

 /*
  * OK, we can do the query this way.  Prepare to create a MinMaxAggPath
  * node.
  *
  * First, create an output Param node for each agg.  (If we end up not
  * using the MinMaxAggPath, we'll waste a PARAM_EXEC slot for each agg,
  * which is not worth worrying about.  We can't wait till create_plan time
  * to decide whether to make the Param, unfortunately.)
 */

 foreach(lc, aggs_list)
 {
  MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);

  mminfo->param =
   SS_make_initplan_output_param(root,
            exprType((Node *) mminfo->target),
            -1,
            exprCollation((Node *) mminfo->target));
 }

 /*
  * Create a MinMaxAggPath node with the appropriate estimated costs and
  * other needed data, and add it to the UPPERREL_GROUP_AGG upperrel, where
  * it will compete against the standard aggregate implementation.  (It
  * will likely always win, but we need not assume that here.)
  *
  * Note: grouping_planner won't have created this upperrel yet, but it's
  * fine for us to create it first.  We will not have inserted the correct
  * consider_parallel value in it, but MinMaxAggPath paths are currently
  * never parallel-safe anyway, so that doesn't matter.  Likewise, it
  * doesn't matter that we haven't filled FDW-related fields in the rel.
  * Also, because there are no rowmarks, we know that the processed_tlist
  * doesn't need to change anymore, so making the pathtarget now is safe.
 */

 grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
 add_path(grouped_rel, (Path *)
    create_minmaxagg_path(root, grouped_rel,
           create_pathtarget(root,
              root->processed_tlist),
           aggs_list,
           (List *) parse->havingQual));
}

/*
 * can_minmax_aggs
 *  Examine all the aggregates in the query, and check if they are
 *  all MIN/MAX aggregates.  If so, build a list of MinMaxAggInfo
 *  nodes for them.
 *
 * Returns false if a non-MIN/MAX aggregate is found, true otherwise.
 */

static bool
can_minmax_aggs(PlannerInfo *root, List **context)
{
 ListCell   *lc;

 /*
  * This function used to have to scan the query for itself, but now we can
  * just thumb through the AggInfo list made by preprocess_aggrefs.
 */

 foreach(lc, root->agginfos)
 {
  AggInfo    *agginfo = lfirst_node(AggInfo, lc);
  Aggref    *aggref = linitial_node(Aggref, agginfo->aggrefs);
  Oid   aggsortop;
  TargetEntry *curTarget;
  MinMaxAggInfo *mminfo;

  Assert(aggref->agglevelsup == 0);
  if (list_length(aggref->args) != 1)
   return false;  /* it couldn't be MIN/MAX */

  /*
   * ORDER BY is usually irrelevant for MIN/MAX, but it can change the
   * outcome if the aggsortop's operator class recognizes non-identical
   * values as equal.  For example, 4.0 and 4.00 are equal according to
   * numeric_ops, yet distinguishable.  If MIN() receives more than one
   * value equal to 4.0 and no value less than 4.0, it is unspecified
   * which of those equal values MIN() returns.  An ORDER BY expression
   * that differs for each of those equal values of the argument
   * expression makes the result predictable once again.  This is a
   * niche requirement, and we do not implement it with subquery paths.
   * In any case, this test lets us reject ordered-set aggregates
   * quickly.
 */

  if (aggref->aggorder != NIL)
   return false;
  /* note: we do not care if DISTINCT is mentioned ... */

  /*
   * We might implement the optimization when a FILTER clause is present
   * by adding the filter to the quals of the generated subquery.  For
   * now, just punt.
 */

  if (aggref->aggfilter != NULL)
   return false;

  aggsortop = fetch_agg_sort_op(aggref->aggfnoid);
  if (!OidIsValid(aggsortop))
   return false;  /* not a MIN/MAX aggregate */

  curTarget = (TargetEntry *) linitial(aggref->args);

  if (contain_mutable_functions((Node *) curTarget->expr))
   return false;  /* not potentially indexable */

  if (type_is_rowtype(exprType((Node *) curTarget->expr)))
   return false;  /* IS NOT NULL would have weird semantics */

  mminfo = makeNode(MinMaxAggInfo);
  mminfo->aggfnoid = aggref->aggfnoid;
  mminfo->aggsortop = aggsortop;
  mminfo->target = curTarget->expr;
  mminfo->subroot = NULL; /* don't compute path yet */
  mminfo->path = NULL;
  mminfo->pathcost = 0;
  mminfo->param = NULL;

  *context = lappend(*context, mminfo);
 }
 return true;
}

/*
 * build_minmax_path
 *  Given a MIN/MAX aggregate, try to build an indexscan Path it can be
 *  optimized with.
 *
 * If successful, stash the best path in *mminfo and return true.
 * Otherwise, return false.
 */

static bool
build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
      Oid eqop, Oid sortop, bool reverse_sort, bool nulls_first)
{
 PlannerInfo *subroot;
 Query    *parse;
 TargetEntry *tle;
 List    *tlist;
 NullTest   *ntest;
 SortGroupClause *sortcl;
 RelOptInfo *final_rel;
 Path    *sorted_path;
 Cost  path_cost;
 double  path_fraction;

 /*
  * We are going to construct what is effectively a sub-SELECT query, so
  * clone the current query level's state and adjust it to make it look
  * like a subquery.  Any outer references will now be one level higher
  * than before.  (This means that when we are done, there will be no Vars
  * of level 1, which is why the subquery can become an initplan.)
 */

 subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo));
 memcpy(subroot, root, sizeof(PlannerInfo));
 subroot->query_level++;
 subroot->parent_root = root;
 /* reset subplan-related stuff */
 subroot->plan_params = NIL;
 subroot->outer_params = NULL;
 subroot->init_plans = NIL;
 subroot->agginfos = NIL;
 subroot->aggtransinfos = NIL;

 subroot->parse = parse = copyObject(root->parse);
 IncrementVarSublevelsUp((Node *) parse, 11);

 /* append_rel_list might contain outer Vars? */
 subroot->append_rel_list = copyObject(root->append_rel_list);
 IncrementVarSublevelsUp((Node *) subroot->append_rel_list, 11);
 /* There shouldn't be any OJ info to translate, as yet */
 Assert(subroot->join_info_list == NIL);
 /* and we haven't made equivalence classes, either */
 Assert(subroot->eq_classes == NIL);
 /* and we haven't created PlaceHolderInfos, either */
 Assert(subroot->placeholder_list == NIL);

 /*----------
  * Generate modified query of the form
  *  (SELECT col FROM tab
  *   WHERE col IS NOT NULL AND existing-quals
  *   ORDER BY col ASC/DESC
  *   LIMIT 1)
  *----------
 */

 /* single tlist entry that is the aggregate target */
 tle = makeTargetEntry(copyObject(mminfo->target),
        (AttrNumber) 1,
        pstrdup("agg_target"),
        false);
 tlist = list_make1(tle);
 subroot->processed_tlist = parse->targetList = tlist;

 /* No HAVING, no DISTINCT, no aggregates anymore */
 parse->havingQual = NULL;
 subroot->hasHavingQual = false;
 parse->distinctClause = NIL;
 parse->hasDistinctOn = false;
 parse->hasAggs = false;

 /* Build "target IS NOT NULL" expression */
 ntest = makeNode(NullTest);
 ntest->nulltesttype = IS_NOT_NULL;
 ntest->arg = copyObject(mminfo->target);
 /* we checked it wasn't a rowtype in can_minmax_aggs */
 ntest->argisrow = false;
 ntest->location = -1;

 /* User might have had that in WHERE already */
 if (!list_member((List *) parse->jointree->quals, ntest))
  parse->jointree->quals = (Node *)
   lcons(ntest, (List *) parse->jointree->quals);

 /* Build suitable ORDER BY clause */
 sortcl = makeNode(SortGroupClause);
 sortcl->tleSortGroupRef = assignSortGroupRef(tle, subroot->processed_tlist);
 sortcl->eqop = eqop;
 sortcl->sortop = sortop;
 sortcl->reverse_sort = reverse_sort;
 sortcl->nulls_first = nulls_first;
 sortcl->hashable = false/* no need to make this accurate */
 parse->sortClause = list_make1(sortcl);

 /* set up expressions for LIMIT 1 */
 parse->limitOffset = NULL;
 parse->limitCount = (Node *) makeConst(INT8OID, -1, InvalidOid,
             sizeof(int64),
             Int64GetDatum(1), false,
             FLOAT8PASSBYVAL);

 /*
  * Generate the best paths for this query, telling query_planner that we
  * have LIMIT 1.
 */

 subroot->tuple_fraction = 1.0;
 subroot->limit_tuples = 1.0;

 final_rel = query_planner(subroot, minmax_qp_callback, NULL);

 /*
  * Since we didn't go through subquery_planner() to handle the subquery,
  * we have to do some of the same cleanup it would do, in particular cope
  * with params and initplans used within this subquery.  (This won't
  * matter if we end up not using the subplan.)
 */

 SS_identify_outer_params(subroot);
 SS_charge_for_initplans(subroot, final_rel);

 /*
  * Get the best presorted path, that being the one that's cheapest for
  * fetching just one row.  If there's no such path, fail.
 */

 if (final_rel->rows > 1.0)
  path_fraction = 1.0 / final_rel->rows;
 else
  path_fraction = 1.0;

 sorted_path =
  get_cheapest_fractional_path_for_pathkeys(final_rel->pathlist,
              subroot->query_pathkeys,
              NULL,
              path_fraction);
 if (!sorted_path)
  return false;

 /*
  * The path might not return exactly what we want, so fix that.  (We
  * assume that this won't change any conclusions about which was the
  * cheapest path.)
 */

 sorted_path = apply_projection_to_path(subroot, final_rel, sorted_path,
             create_pathtarget(subroot,
                subroot->processed_tlist));

 /*
  * Determine cost to get just the first row of the presorted path.
  *
  * Note: cost calculation here should match
  * compare_fractional_path_costs().
 */

 path_cost = sorted_path->startup_cost +
  path_fraction * (sorted_path->total_cost - sorted_path->startup_cost);

 /* Save state for further processing */
 mminfo->subroot = subroot;
 mminfo->path = sorted_path;
 mminfo->pathcost = path_cost;

 return true;
}

/*
 * Compute query_pathkeys and other pathkeys during query_planner()
 */

static void
minmax_qp_callback(PlannerInfo *root, void *extra)
{
 root->group_pathkeys = NIL;
 root->window_pathkeys = NIL;
 root->distinct_pathkeys = NIL;

 root->sort_pathkeys =
  make_pathkeys_for_sortclauses(root,
           root->parse->sortClause,
           root->parse->targetList);

 root->query_pathkeys = root->sort_pathkeys;
}

/*
 * Get the OID of the sort operator, if any, associated with an aggregate.
 * Returns InvalidOid if there is no such operator.
 */

static Oid
fetch_agg_sort_op(Oid aggfnoid)
{
 HeapTuple aggTuple;
 Form_pg_aggregate aggform;
 Oid   aggsortop;

 /* fetch aggregate entry from pg_aggregate */
 aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(aggfnoid));
 if (!HeapTupleIsValid(aggTuple))
  return InvalidOid;
 aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
 aggsortop = aggform->aggsortop;
 ReleaseSysCache(aggTuple);

 return aggsortop;
}

Messung V0.5 in Prozent
C=94 H=93 G=93

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.56Bemerkung:  (vorverarbeitet am  2026-08-08) ¤

*Bot Zugriff






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.