From 53232a4274a61d80a915b182645c23d7ca81a923 Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Thu, 22 Oct 2026 08:30:18 +0200 Subject: [PATCH] optimizer: Introduce row override mechanism to steer row estimates This allows extensions to supply one or multiple row overrides in PlannerInfo that are tied to a base or join rel, and either set an absolute row count, or multiply the row count from selectivity estimates by a given factor. Author: Lukas Fittl Reviewed-by: Discussion: --- src/backend/optimizer/path/allpaths.c | 67 +++++++++ src/backend/optimizer/path/costsize.c | 172 +++++++++++++++++++++- src/backend/optimizer/plan/analyzejoins.c | 16 ++ src/backend/optimizer/plan/createplan.c | 10 ++ src/backend/optimizer/plan/planner.c | 7 + src/backend/optimizer/util/relnode.c | 7 +- src/include/nodes/pathnodes.h | 73 +++++++++ src/include/nodes/plannodes.h | 7 + src/include/optimizer/cost.h | 4 + src/tools/pgindent/typedefs.list | 2 + 10 files changed, 362 insertions(+), 3 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 295e3509c740b..988ea961bb4e5 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -115,6 +115,8 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void scale_appendrel_children(PlannerInfo *root, RelOptInfo *rel, + double factor); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -1001,6 +1003,41 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) rel->fdwroutine->GetForeignPaths(root, rel, rte->relid); } +/* + * scale_appendrel_children + * Multiply the row estimates of all live children of an appendrel (and + * recursively their children) by factor, in support of an absolute rows + * override on the appendrel. + * + * Children that have already built their paths, such as subqueries and CTEs, + * are left alone: their paths copied the earlier estimate, and adjusting the + * rel now would only make the two disagree. + */ +static void +scale_appendrel_children(PlannerInfo *root, RelOptInfo *rel, double factor) +{ + ListCell *l; + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + RelOptInfo *childrel; + + if (appinfo->parent_relid != rel->relid) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + if (IS_DUMMY_REL(childrel) || childrel->pathlist != NIL) + continue; + + childrel->rows = clamp_row_est(childrel->rows * factor); + childrel->rows_scale *= factor; + + scale_appendrel_children(root, childrel, factor); + } +} + /* * set_append_rel_size * Set size estimates for a simple "append relation" @@ -1023,6 +1060,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double parent_size; double *parent_attrsizes; int nattrs; + RowsOverride *override; ListCell *l; /* Guard against stack overflow due to overly deep inheritance tree. */ @@ -1071,6 +1109,19 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, nattrs = rel->max_attr - rel->min_attr + 1; parent_attrsizes = palloc0_array(double, nattrs); + /* + * Look for a rows override for the appendrel as a whole. A + * multiplicative override, or a factor inherited from a parent appendrel, + * is recorded in rows_scale right away so that the children pick it up as + * they are sized. An absolute override cannot be applied until the + * children's estimates have been summed; see below. + */ + override = find_rows_override(root, rel->relids); + if (override != NULL && override->kind == ROWS_OVERRIDE_MULTIPLY) + rel->rows_scale = override->value; + else if (override == NULL) + rel->rows_scale = inherited_rows_scale(root, rel); + foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -1294,6 +1345,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, for (i = 0; i < nattrs; i++) rel->attr_widths[i] = rint(parent_attrsizes[i] / parent_rows); + /* + * Now that the standard total is known, apply an absolute rows + * override. Append paths sum the row counts of their subpaths, so + * the resulting factor is distributed over the children to keep the + * two in agreement. + */ + if (override != NULL && override->kind == ROWS_OVERRIDE_ABSOLUTE) + { + double new_rows = clamp_row_est(override->value); + double factor = new_rows / parent_rows; + + rel->rows = new_rows; + rel->rows_scale = factor; + scale_appendrel_children(root, rel, factor); + } + /* * Note that we leave rel->pages as zero; this is important to avoid * double-counting the appendrel tree in total_table_pages. diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 7bbddb8bee4fa..f8aafbc74a7f2 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -205,6 +205,7 @@ static int32 get_expr_width(PlannerInfo *root, const Node *expr); static double relation_byte_size(double tuples, int width); static double page_size(double tuples, int width); static double get_parallel_divisor(Path *path); +static Relids rows_override_relids(PlannerInfo *root, Relids relids); /* @@ -5590,6 +5591,161 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals) } +/* + * rows_override_relids + * Reduce relids to the set that identifies a relation for the purpose + * of rows overrides. + * + * Join relations carry the relids of the outer joins they have completed in + * addition to their member base relations (see outer_join_rels). Callers + * registering an override only know the base relations, and typically run + * before the outer-join relids have even been assigned, so overrides are + * matched on base relids alone. + */ +static Relids +rows_override_relids(PlannerInfo *root, Relids relids) +{ + if (root->outer_join_rels != NULL && + bms_overlap(relids, root->outer_join_rels)) + return bms_difference(relids, root->outer_join_rels); + return relids; +} + +/* + * add_rows_override + * Register a row count override for the relation identified by relids. + * + * This is the interface for extensions; see the comments for RowsOverride in + * pathnodes.h for the semantics. The relids set is copied. + */ +void +add_rows_override(PlannerInfo *root, Relids relids, + RowsOverrideKind kind, double value) +{ + RowsOverride *override; + + relids = rows_override_relids(root, relids); + if (bms_is_empty(relids)) + elog(ERROR, "rows override must apply to at least one relation"); + if (isnan(value) || isinf(value) || value < 0.0) + elog(ERROR, "rows override value must be a non-negative finite number"); + if (kind == ROWS_OVERRIDE_MULTIPLY && value == 0.0) + elog(ERROR, "rows override multiplier must be positive"); + + override = makeNode(RowsOverride); + override->relids = bms_copy(relids); + override->kind = kind; + override->value = value; + + root->rows_overrides = lappend(root->rows_overrides, override); +} + +/* + * find_rows_override + * Return the most recently registered override for the relation + * identified by relids, or NULL if there is none. + * + * Outer-join relids in the given set are ignored, so that the relids of a + * join relation match an override registered for its base relations. + */ +RowsOverride * +find_rows_override(PlannerInfo *root, Relids relids) +{ + RowsOverride *result = NULL; + ListCell *lc; + + if (root->rows_overrides == NIL) + return NULL; + + relids = rows_override_relids(root, relids); + + foreach(lc, root->rows_overrides) + { + RowsOverride *override = lfirst_node(RowsOverride, lc); + + if (bms_equal(override->relids, relids)) + result = override; + } + + return result; +} + +/* + * inherited_rows_scale + * Return the rows_scale factor a child relation inherits from its + * topmost parent, or 1.0 if it has no parent or the parent is unknown. + * + * For child join relations the parent join relation has already been sized + * by the time the child is built, so its factor is final. For child base + * relations the parent's factor is only known in advance for multiplicative + * overrides; set_append_rel_size() takes care of absolute ones after it has + * summed the children. + */ +double +inherited_rows_scale(PlannerInfo *root, RelOptInfo *rel) +{ + RelOptInfo *parent; + + if (rel->top_parent_relids == NULL) + return 1.0; + + if (IS_JOIN_REL(rel)) + parent = find_join_rel(root, rel->top_parent_relids); + else + parent = find_base_rel(root, + bms_singleton_member(rel->top_parent_relids)); + + if (parent == NULL) + return 1.0; + + return parent->rows_scale; +} + +/* + * apply_rows_override + * Adjust rel->rows according to any registered override, and set + * rel->rows_scale accordingly. + * + * Must be called after the standard estimate has been stored in rel->rows. + * Grouped relations share relids with the relation they were derived from, + * so overrides are never applied to them. + */ +static void +apply_rows_override(PlannerInfo *root, RelOptInfo *rel) +{ + double standard_rows = rel->rows; + double new_rows; + RowsOverride *override; + + rel->rows_scale = 1.0; + + if (root->rows_overrides == NIL || IS_GROUPED_REL(rel)) + return; + + Assert(standard_rows > 0); + + override = find_rows_override(root, rel->relids); + if (override != NULL) + { + if (override->kind == ROWS_OVERRIDE_ABSOLUTE) + new_rows = override->value; + else + new_rows = standard_rows * override->value; + } + else + { + double scale = inherited_rows_scale(root, rel); + + if (scale == 1.0) + return; + new_rows = standard_rows * scale; + } + + new_rows = clamp_row_est(new_rows); + rel->rows = new_rows; + rel->rows_scale = new_rows / standard_rows; +} + /* * set_baserel_size_estimates * Set the size estimates for the given base relation. @@ -5600,6 +5756,7 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals) * We set the following fields of the rel node: * rows: the estimated number of output tuples (after applying * restriction clauses). + * rows_scale: the factor by which a rows override changed rows, if any. * width: the estimated average output tuple width in bytes. * baserestrictcost: estimated cost of evaluating baserestrictinfo clauses. */ @@ -5620,6 +5777,8 @@ set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel) rel->rows = clamp_row_est(nrows); + apply_rows_override(root, rel); + cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root); set_rel_width(root, rel); @@ -5654,6 +5813,9 @@ get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel, JOIN_INNER, NULL); nrows = clamp_row_est(nrows); + /* Apply the same correction a rows override made to the base estimate */ + if (rel->rows_scale != 1.0) + nrows = clamp_row_est(nrows * rel->rows_scale); /* For safety, make sure result is not more than the base estimate */ if (nrows > rel->rows) nrows = rel->rows; @@ -5679,8 +5841,9 @@ get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel, * anyway we must keep the rowcount estimate the same for all paths for the * joinrel.) * - * We set only the rows field here. The reltarget field was already set by - * build_joinrel_tlist, and baserestrictcost is not used for join rels. + * We set only the rows and rows_scale fields here. The reltarget field was + * already set by build_joinrel_tlist, and baserestrictcost is not used for + * join rels. */ void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, @@ -5697,6 +5860,8 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, inner_rel->rows, sjinfo, restrictlist); + + apply_rows_override(root, rel); } /* @@ -5740,6 +5905,9 @@ get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel, inner_path->rows, sjinfo, restrict_clauses); + /* Apply the same correction a rows override made to the base estimate */ + if (rel->rows_scale != 1.0) + nrows = clamp_row_est(nrows * rel->rows_scale); /* For safety, make sure result is not more than the base estimate */ if (nrows > rel->rows) nrows = rel->rows; diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 7aad91fd34e19..77889f779ef82 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -1271,6 +1271,22 @@ remove_self_join_rel(PlannerInfo *root, ChangeVarNodes((Node *) root->append_rel_list, toRemove->relid, toKeep->relid, 0); + /* + * Transfer any rows overrides mentioning the removed relation to the kept + * one, so that an override registered for either side of the self join + * still applies to the scan that survives. + */ + foreach_node(RowsOverride, override, root->rows_overrides) + { + if (bms_is_member(toRemove->relid, override->relids)) + { + override->relids = bms_del_member(override->relids, + toRemove->relid); + override->relids = bms_add_member(override->relids, + toKeep->relid); + } + } + /* * As in remove_useless_outer_joins, discard the securityQuals of the * removed rel, so that later passes don't visit them. We know the kept diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 2f8a08ad78e56..e3b76e7a56553 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1939,6 +1939,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags) plan->total_cost = best_path->path.total_cost; plan->plan_rows = best_path->path.rows; plan->plan_width = best_path->path.pathtarget->width; + plan->rows_scale = best_path->path.parent->rows_scale; plan->parallel_safe = best_path->path.parallel_safe; /* ... but don't change subplan's parallel_aware flag */ } @@ -2436,6 +2437,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) sqplan->startup_cost = mminfo->path->startup_cost; sqplan->total_cost = mminfo->pathcost; sqplan->plan_rows = 1; + sqplan->rows_scale = 1.0; sqplan->plan_width = mminfo->path->pathtarget->width; sqplan->parallel_aware = false; sqplan->parallel_safe = mminfo->path->parallel_safe; @@ -3217,6 +3219,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, plan->plan_rows = clamp_row_est(apath->bitmapselectivity * apath->path.parent->tuples); plan->plan_width = 0; /* meaningless */ + plan->rows_scale = 1.0; plan->parallel_aware = false; plan->parallel_safe = apath->path.parallel_safe; *qual = subquals; @@ -3281,6 +3284,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, plan->plan_rows = clamp_row_est(opath->bitmapselectivity * opath->path.parent->tuples); plan->plan_width = 0; /* meaningless */ + plan->rows_scale = 1.0; plan->parallel_aware = false; plan->parallel_safe = opath->path.parallel_safe; } @@ -3328,6 +3332,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, plan->plan_rows = clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples); plan->plan_width = 0; /* meaningless */ + plan->rows_scale = 1.0; plan->parallel_aware = false; plan->parallel_safe = ipath->path.parallel_safe; /* Extract original index clauses, actual index quals, relevant ECs */ @@ -5421,6 +5426,7 @@ copy_generic_path_info(Plan *dest, Path *src) dest->total_cost = src->total_cost; dest->plan_rows = src->rows; dest->plan_width = src->pathtarget->width; + dest->rows_scale = src->parent->rows_scale; dest->parallel_aware = src->parallel_aware; dest->parallel_safe = src->parallel_safe; } @@ -5437,6 +5443,7 @@ copy_plan_costsize(Plan *dest, Plan *src) dest->total_cost = src->total_cost; dest->plan_rows = src->plan_rows; dest->plan_width = src->plan_width; + dest->rows_scale = src->rows_scale; /* Assume the inserted node is not parallel-aware. */ dest->parallel_aware = false; /* Assume the inserted node is parallel-safe, if child plan is. */ @@ -5472,6 +5479,7 @@ label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples) plan->plan.total_cost = sort_path.total_cost; plan->plan.plan_rows = lefttree->plan_rows; plan->plan.plan_width = lefttree->plan_width; + plan->plan.rows_scale = lefttree->rows_scale; plan->plan.parallel_aware = false; plan->plan.parallel_safe = lefttree->parallel_safe; } @@ -5504,6 +5512,7 @@ label_incrementalsort_with_costsize(PlannerInfo *root, IncrementalSort *plan, plan->sort.plan.total_cost = sort_path.total_cost; plan->sort.plan.plan_rows = lefttree->plan_rows; plan->sort.plan.plan_width = lefttree->plan_width; + plan->sort.plan.rows_scale = lefttree->rows_scale; plan->sort.plan.parallel_aware = false; plan->sort.plan.parallel_safe = lefttree->parallel_safe; } @@ -6607,6 +6616,7 @@ materialize_finished_plan(Plan *subplan) matplan->total_cost = matpath.total_cost + initplan_cost; matplan->plan_rows = subplan->plan_rows; matplan->plan_width = subplan->plan_width; + matplan->rows_scale = subplan->rows_scale; matplan->parallel_aware = false; matplan->parallel_safe = subplan->parallel_safe; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index edaa42d713af0..ccc909a7cb269 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -608,6 +608,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, parallel_setup_cost + parallel_tuple_cost * top_plan->plan_rows; gather->plan.plan_rows = top_plan->plan_rows; gather->plan.plan_width = top_plan->plan_width; + gather->plan.rows_scale = top_plan->rows_scale; gather->plan.parallel_aware = false; gather->plan.parallel_safe = false; @@ -8746,6 +8747,9 @@ create_unique_paths(PlannerInfo *root, RelOptInfo *rel, SpecialJoinInfo *sjinfo) unique_rel = makeNode(RelOptInfo); memcpy(unique_rel, rel, sizeof(RelOptInfo)); + /* rows overrides never apply to unique rels; see build_grouped_rel() */ + unique_rel->rows_scale = 1.0; + /* * clear path info */ @@ -9120,6 +9124,9 @@ create_partial_unique_paths(PlannerInfo *root, RelOptInfo *input_rel, partial_unique_rel = makeNode(RelOptInfo); memcpy(partial_unique_rel, input_rel, sizeof(RelOptInfo)); + /* rows overrides never apply to unique rels; see build_grouped_rel() */ + partial_unique_rel->rows_scale = 1.0; + /* * clear path info */ diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index f2ccba52f204c..343b5410611a9 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -230,6 +230,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->reloptkind = parent ? RELOPT_OTHER_MEMBER_REL : RELOPT_BASEREL; rel->relids = bms_make_singleton(relid); rel->rows = 0; + rel->rows_scale = 1.0; /* cheap startup cost is interesting iff not all tuples to be retrieved */ rel->consider_startup = (root->tuple_fraction > 0); rel->consider_param_startup = false; /* might get changed later */ @@ -539,9 +540,10 @@ build_grouped_rel(PlannerInfo *root, RelOptInfo *rel) grouped_rel->fdw_private = NULL; /* - * clear size estimates + * clear size estimates (rows overrides never apply to grouped rels) */ grouped_rel->rows = 0; + grouped_rel->rows_scale = 1.0; return grouped_rel; } @@ -868,6 +870,7 @@ build_join_rel(PlannerInfo *root, joinrel->reloptkind = RELOPT_JOINREL; joinrel->relids = bms_copy(joinrelids); joinrel->rows = 0; + joinrel->rows_scale = 1.0; /* cheap startup cost is interesting iff not all tuples to be retrieved */ joinrel->consider_startup = (root->tuple_fraction > 0); joinrel->consider_param_startup = false; @@ -1075,6 +1078,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, joinrel->relids = adjust_child_relids(parent_joinrel->relids, nappinfos, appinfos); joinrel->rows = 0; + joinrel->rows_scale = 1.0; /* cheap startup cost is interesting iff not all tuples to be retrieved */ joinrel->consider_startup = (root->tuple_fraction > 0); joinrel->consider_param_startup = false; @@ -1697,6 +1701,7 @@ fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids) upperrel = makeNode(RelOptInfo); upperrel->reloptkind = RELOPT_UPPER_REL; upperrel->relids = bms_copy(relids); + upperrel->rows_scale = 1.0; upperrel->pgs_mask = root->glob->default_pgs_mask; /* cheap startup cost is interesting iff not all tuples to be retrieved */ diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 8615db98a551b..7a78330eedd1c 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -102,6 +102,71 @@ */ typedef Bitmapset *Relids; +/* + * Rows overrides + * + * Extensions can supply row count estimates for specific base or join + * relations by adding RowsOverride entries to PlannerInfo's rows_overrides + * list (see add_rows_override()). Each entry is keyed by the set of base + * relids that identifies the target relation at the current query level; + * outer-join relids (see PlannerInfo.outer_join_rels) are ignored both when + * registering and when looking up an entry, so that a join relation is + * matched by its member base relations regardless of which outer joins it + * has completed. + * When the planner computes the standard size estimate for a relation, it + * looks for a matching entry and, if one is found, replaces the estimate + * (absolute override) or scales it (multiplicative override). If several + * entries match, the one added most recently wins. + * + * The planner records the ratio of the final estimate to the standard + * estimate in RelOptInfo's rows_scale field. That factor is applied to the + * estimates for parameterized paths of the same relation, so that an + * override given for a relation also affects the inner side of nested loops. + * It is also copied into the rows_scale field of the Plan nodes created for + * the relation, so that the executor and EXPLAIN can recover the standard + * estimate as plan_rows / rows_scale. + * Child relations produced by inheritance expansion or partitionwise join + * inherit the factor from their parent unless they have an override of their + * own; an absolute value given for an append relation is distributed + * proportionally over its children (except children that have already built + * their paths by then, such as subqueries that could not be flattened, which + * keep their own estimates). Note that a foreign data wrapper's + * GetForeignRelSize callback runs after an override has been applied and may + * replace the estimate. + * + * Overrides must be registered before the target relation's size is + * estimated. The build_simple_rel_hook is a convenient place to do so, since + * it runs after the range table is complete but before any relation is sized. + * The list is not cleared when query_planner() restarts after simplifying the + * join tree: range table indexes are stable across restarts, so existing + * entries stay valid, and registering an entry for the same relids again is + * harmless. When self-join elimination merges two relations, entries that + * mention the removed relation are rewritten to mention the kept one. + * Entries for relations removed by other join simplifications simply never + * match anything. + */ +typedef enum RowsOverrideKind +{ + ROWS_OVERRIDE_ABSOLUTE, /* use value as the row count estimate */ + ROWS_OVERRIDE_MULTIPLY, /* multiply the standard estimate by value */ +} RowsOverrideKind; + +typedef struct RowsOverride +{ + pg_node_attr(no_copy_equal, no_read, no_query_jumble) + + NodeTag type; + + /* relids of the base or join relation this override applies to */ + Relids relids; + + /* how to combine value with the planner's own estimate */ + RowsOverrideKind kind; + + /* the row count or multiplier */ + double value; +} RowsOverride; + /* * When looking for a "cheapest path", this enum specifies whether we want * cheapest startup cost or cheapest total cost. @@ -690,6 +755,12 @@ struct PlannerInfo /* PartitionPruneInfos added in this query's plan. */ List *partPruneInfos; + /* + * Row count overrides supplied by extensions; list of RowsOverride. See + * the comments for RowsOverride. + */ + List *rows_overrides; + /* extension state */ void **extension_state pg_node_attr(read_write_ignore); int extension_state_allocated; @@ -1025,6 +1096,8 @@ typedef struct RelOptInfo */ /* estimated number of result tuples */ Cardinality rows; + /* ratio of rows to the standard estimate, per rows override; 1.0 if none */ + double rows_scale; /* * per-relation planner control diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 9e538f3c44a3c..0ee5eaa9185fa 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -212,6 +212,13 @@ typedef struct Plan /* average row width in bytes */ int plan_width; + /* + * ratio of plan_rows to the estimate the planner would have made without + * any rows override (see RowsOverride in pathnodes.h); 1.0 if no override + * was applied to the relation this node was created for + */ + double rows_scale; + /* * information needed for parallel query */ diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 14255c900fc82..ef69ba62c2e27 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -195,6 +195,10 @@ extern void compute_semi_anti_join_factors(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *restrictlist, SemiAntiJoinFactors *semifactors); +extern void add_rows_override(PlannerInfo *root, Relids relids, + RowsOverrideKind kind, double value); +extern RowsOverride *find_rows_override(PlannerInfo *root, Relids relids); +extern double inherited_rows_scale(PlannerInfo *root, RelOptInfo *rel); extern void set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern double get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index a7a8dbdd23f79..9060103c29612 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2706,6 +2706,8 @@ RowExpr RowIdentityVarInfo RowMarkClause RowMarkType +RowsOverride +RowsOverrideKind RowSecurityDesc RowSecurityPolicy RtlGetLastNtStatus_t