diff --git a/doc/src/sgml/ref/alter_statistics.sgml b/doc/src/sgml/ref/alter_statistics.sgml index 58c7ed020d..987f9dbc6f 100644 --- a/doc/src/sgml/ref/alter_statistics.sgml +++ b/doc/src/sgml/ref/alter_statistics.sgml @@ -26,6 +26,7 @@ PostgreSQL documentation ALTER STATISTICS name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } ALTER STATISTICS name RENAME TO new_name ALTER STATISTICS name SET SCHEMA new_schema +ALTER STATISTICS name SET STATISTICS new_target @@ -93,6 +94,22 @@ ALTER STATISTICS name SET SCHEMA + + new_target + + + The statistic-gathering target for this statistic object for subsequent + operations. + The target can be set in the range 0 to 10000; alternatively, set it + to -1 to revert to using the system default statistics + target (). + For more information on the use of statistics by the + PostgreSQL query planner, refer to + . + + + + diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index d7004e5313..caa4fca99d 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -490,6 +490,19 @@ do_analyze_rel(Relation onerel, VacuumParams *params, } } + /* + * Look at extended statistic objects too, because those may define their + * own statistic target. So we need to sample enough rows and then build + * the statistics with enough detail. + */ + { + int nrows = ComputeExtStatisticsTarget(onerel, + attr_cnt, vacattrstats); + + if (targrows < nrows) + targrows = nrows; + } + /* * Acquire the sample rows */ diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index cf406f6f96..356b6096ac 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "access/heapam.h" #include "access/relation.h" #include "access/relscan.h" #include "access/table.h" @@ -21,6 +22,7 @@ #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/namespace.h" +#include "catalog/objectaccess.h" #include "catalog/pg_namespace.h" #include "catalog/pg_statistic_ext.h" #include "catalog/pg_statistic_ext_data.h" @@ -29,6 +31,7 @@ #include "miscadmin.h" #include "statistics/statistics.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -336,6 +339,7 @@ CreateStatistics(CreateStatsStmt *stmt) values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid); values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname); values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId); + values[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(-1); values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner); values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys); values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind); @@ -414,6 +418,85 @@ CreateStatistics(CreateStatsStmt *stmt) return myself; } + +/* + * ALTER STATISTICS + */ +ObjectAddress +AlterStatistics(AlterStatsStmt *stmt) +{ + Relation rel; + Oid stxoid; + HeapTuple oldtup; + HeapTuple newtup; + Datum repl_val[Natts_pg_statistic_ext]; + bool repl_null[Natts_pg_statistic_ext]; + bool repl_repl[Natts_pg_statistic_ext]; + ObjectAddress address; + int newtarget = stmt->stxstattarget; + + /* Limit target to a sane range */ + if (newtarget < -1) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("statistics target %d is too low", + newtarget))); + } + else if (newtarget > 10000) + { + newtarget = 10000; + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lowering statistics target to %d", + newtarget))); + } + + /* lookup OID of the statistic object */ + stxoid = get_statistics_object_oid(stmt->defnames, false); + + /* Search pg_statistic_ext */ + rel = table_open(StatisticExtRelationId, RowExclusiveLock); + + oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid)); + + /* Must be owner of the existing statistic object */ + if (!pg_statistics_object_ownercheck(stxoid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_STATISTIC_EXT, + NameListToString(stmt->defnames)); + + /* Build new tuple. */ + memset(repl_val, 0, sizeof(repl_val)); + memset(repl_null, false, sizeof(repl_null)); + memset(repl_repl, false, sizeof(repl_repl)); + + /* replace the stxstattarget column */ + repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true; + repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(newtarget); + + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + repl_val, repl_null, repl_repl); + + /* Update system catalog. */ + CatalogTupleUpdate(rel, &newtup->t_self, newtup); + + InvokeObjectPostAlterHook(StatisticExtRelationId, stxoid, 0); + + ObjectAddressSet(address, StatisticExtRelationId, stxoid); + + /* + * NOTE: because we only support altering the statistic target, not the + * other fields, there is no need to update dependencies. + */ + + heap_freetuple(newtup); + ReleaseSysCache(oldtup); + + table_close(rel, RowExclusiveLock); + + return address; +} + /* * Guts of statistics object deletion. */ diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 78deade89b..048239fe34 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3493,6 +3493,18 @@ _copyCreateStatsStmt(const CreateStatsStmt *from) return newnode; } +static AlterStatsStmt * +_copyAlterStatsStmt(const AlterStatsStmt *from) +{ + AlterStatsStmt *newnode = makeNode(AlterStatsStmt); + + COPY_NODE_FIELD(defnames); + COPY_SCALAR_FIELD(stxstattarget); + COPY_SCALAR_FIELD(if_not_exists); + + return newnode; +} + static CreateFunctionStmt * _copyCreateFunctionStmt(const CreateFunctionStmt *from) { @@ -5249,6 +5261,9 @@ copyObjectImpl(const void *from) case T_CreateStatsStmt: retval = _copyCreateStatsStmt(from); break; + case T_AlterStatsStmt: + retval = _copyAlterStatsStmt(from); + break; case T_CreateFunctionStmt: retval = _copyCreateFunctionStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 4f2ebe5118..4c35cdf41e 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1365,6 +1365,16 @@ _equalCreateStatsStmt(const CreateStatsStmt *a, const CreateStatsStmt *b) return true; } +static bool +_equalAlterStatsStmt(const AlterStatsStmt *a, const AlterStatsStmt *b) +{ + COMPARE_NODE_FIELD(defnames); + COMPARE_SCALAR_FIELD(stxstattarget); + COMPARE_SCALAR_FIELD(if_not_exists); + + return true; +} + static bool _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *b) { @@ -3309,6 +3319,9 @@ equal(const void *a, const void *b) case T_CreateStatsStmt: retval = _equalCreateStatsStmt(a, b); break; + case T_AlterStatsStmt: + retval = _equalAlterStatsStmt(a, b); + break; case T_CreateFunctionStmt: retval = _equalCreateFunctionStmt(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 8400dd319e..4551d7a023 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2662,6 +2662,16 @@ _outCreateStatsStmt(StringInfo str, const CreateStatsStmt *node) WRITE_BOOL_FIELD(if_not_exists); } +static void +_outAlterStatsStmt(StringInfo str, const AlterStatsStmt *node) +{ + WRITE_NODE_TYPE("ALTERSTATSSTMT"); + + WRITE_NODE_FIELD(defnames); + WRITE_INT_FIELD(stxstattarget); + WRITE_BOOL_FIELD(if_not_exists); +} + static void _outNotifyStmt(StringInfo str, const NotifyStmt *node) { @@ -4124,6 +4134,9 @@ outNode(StringInfo str, const void *obj) case T_CreateStatsStmt: _outCreateStatsStmt(str, obj); break; + case T_AlterStatsStmt: + _outAlterStatsStmt(str, obj); + break; case T_NotifyStmt: _outNotifyStmt(str, obj); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 208b4a1f28..29e24fdfea 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -252,7 +252,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt AlterCompositeTypeStmt AlterUserMappingStmt - AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt + AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt CallStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt @@ -852,6 +852,7 @@ stmt : | AlterRoleSetStmt | AlterRoleStmt | AlterSubscriptionStmt + | AlterStatsStmt | AlterTSConfigurationStmt | AlterTSDictionaryStmt | AlterUserMappingStmt @@ -3984,6 +3985,34 @@ CreateStatsStmt: } ; + +/***************************************************************************** + * + * QUERY : + * ALTER STATISTICS [IF EXISTS] stats_name + * SET STATISTICS + * + *****************************************************************************/ + +AlterStatsStmt: + ALTER STATISTICS any_name SET STATISTICS SignedIconst + { + AlterStatsStmt *n = makeNode(AlterStatsStmt); + n->defnames = $3; + n->if_not_exists = false; + n->stxstattarget = $6; + $$ = (Node *)n; + } + | ALTER STATISTICS IF_P EXISTS any_name SET STATISTICS SignedIconst + { + AlterStatsStmt *n = makeNode(AlterStatsStmt); + n->defnames = $5; + n->if_not_exists = true; + n->stxstattarget = $8; + $$ = (Node *)n; + } + ; + /***************************************************************************** * * QUERY : diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index c56ed48270..f0d0deabaf 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -61,6 +61,7 @@ typedef struct StatExtEntry char *name; /* statistics object's name */ Bitmapset *columns; /* attribute numbers covered by the object */ List *types; /* 'char' list of enabled statistic kinds */ + int stattarget; /* statistic target (-1 for default) */ } StatExtEntry; @@ -70,7 +71,8 @@ static VacAttrStats **lookup_var_attr_stats(Relation rel, Bitmapset *attrs, static void statext_store(Oid relid, MVNDistinct *ndistinct, MVDependencies *dependencies, MCVList *mcv, VacAttrStats **stats); - +static int statext_compute_stattarget(int stattarget, + int natts, VacAttrStats **stats); /* * Compute requested extended stats, using the rows sampled for the plain @@ -106,6 +108,7 @@ BuildRelationExtStatistics(Relation onerel, double totalrows, MCVList *mcv = NULL; VacAttrStats **stats; ListCell *lc2; + int stattarget; /* * Check if we can build these stats based on the column analyzed. If @@ -130,6 +133,13 @@ BuildRelationExtStatistics(Relation onerel, double totalrows, Assert(bms_num_members(stat->columns) >= 2 && bms_num_members(stat->columns) <= STATS_MAX_DIMENSIONS); + /* compute current statistic target */ + stattarget = statext_compute_stattarget(stat->stattarget, + bms_num_members(stat->columns), + stats); + + /* XXX What if the target is set to 0? Reset the statistic? */ + /* compute statistic of each requested type */ foreach(lc2, stat->types) { @@ -143,7 +153,7 @@ BuildRelationExtStatistics(Relation onerel, double totalrows, stat->columns, stats); else if (t == STATS_EXT_MCV) mcv = statext_mcv_build(numrows, rows, stat->columns, stats, - totalrows); + totalrows, stattarget); } /* store the statistics in the catalog */ @@ -156,6 +166,108 @@ BuildRelationExtStatistics(Relation onerel, double totalrows, MemoryContextDelete(cxt); } +/* + * ComputeExtStatisticsTarget + * Compute the largest statistic target for all extended statistics. + */ +int +ComputeExtStatisticsTarget(Relation onerel, + int natts, VacAttrStats **vacattrstats) +{ + Relation pg_stext; + ListCell *lc; + List *lstats; + MemoryContext cxt; + MemoryContext oldcxt; + int result = 0; + + cxt = AllocSetContextCreate(CurrentMemoryContext, + "ComputeExtStatisticsTarget", + ALLOCSET_DEFAULT_SIZES); + oldcxt = MemoryContextSwitchTo(cxt); + + pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock); + lstats = fetch_statentries_for_relation(pg_stext, RelationGetRelid(onerel)); + + foreach(lc, lstats) + { + StatExtEntry *stat = (StatExtEntry *) lfirst(lc); + int stattarget = stat->stattarget; + VacAttrStats **stats; + int nattrs = bms_num_members(stat->columns); + + /* + * Check if we can build these stats based on the column analyzed. If + * not, report this fact (except in autovacuum) and move on. + */ + stats = lookup_var_attr_stats(onerel, stat->columns, + natts, vacattrstats); + + /* ignore statistics we'll skip later */ + if (!stats) + continue; + + /* compute statistic target, based on the statistic object and attributes */ + stattarget = statext_compute_stattarget(stat->stattarget, nattrs, stats); + + /* Use the largest value for all statistic objects. */ + if (stattarget > result) + result = stattarget; + } + + table_close(pg_stext, RowExclusiveLock); + + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(cxt); + + return (300 * result); +} + +/* + * statext_compute_stattarget + * compute statistic target for an extended statistic + * + * If the statistic object has custom value (set using ALTER STATISTICS ... + * SET STATISTICS), then we simply use that. Otherwise we look at targets + * for columns the object is defined on and use the largest value. Finally, + * if the target is still (-1), we use the default_statistics_target. + */ +static int +statext_compute_stattarget(int stattarget, int nattrs, VacAttrStats **stats) +{ + int i; + + /* if there's statistic target set for the statistic object, use it */ + if (stattarget >= 0) + return stattarget; + + /* + * The stattarget column is negative, we look at targets for each + * column the statistics is based on, and use use the largest value + * for any of the columns. + */ + { + for (i = 0; i < nattrs; i++) + { + if (stats[i]->attr->attstattarget > stattarget) + stattarget = stats[i]->attr->attstattarget; + } + } + + /* + * If the value is still negative (so neither the extended statistic + * object nor any of the columns) have custom statistic target set. + * In that case use the default target. + */ + if (stattarget < 0) + stattarget = default_statistics_target; + + /* As this point we should have a valid statistic target. */ + Assert((stattarget >= 0) && (stattarget <= 10000)); + + return stattarget; +} + /* * statext_is_kind_built * Is this stat kind built in the given pg_statistic_ext_data tuple? @@ -224,6 +336,7 @@ fetch_statentries_for_relation(Relation pg_statext, Oid relid) entry->statOid = staForm->oid; entry->schema = get_namespace_name(staForm->stxnamespace); entry->name = pstrdup(NameStr(staForm->stxname)); + entry->stattarget = staForm->stxstattarget; for (i = 0; i < staForm->stxkeys.dim1; i++) { entry->columns = bms_add_member(entry->columns, diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c index 913a72ff67..8491b20af3 100644 --- a/src/backend/statistics/mcv.c +++ b/src/backend/statistics/mcv.c @@ -162,7 +162,8 @@ get_mincount_for_mcv_list(int samplerows, double totalrows) */ MCVList * statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs, - VacAttrStats **stats, double totalrows) + VacAttrStats **stats, double totalrows, + int stattarget) { int i, numattrs, @@ -194,12 +195,7 @@ statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs, * Maximum number of MCV items to store, based on the attribute with the * largest stats target (and the number of groups we have available). */ - nitems = stats[0]->attr->attstattarget; - for (i = 1; i < numattrs; i++) - { - if (stats[i]->attr->attstattarget > nitems) - nitems = stats[i]->attr->attstattarget; - } + nitems = stattarget; if (nitems > ngroups) nitems = ngroups; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 05ec7f3ac6..1299066c63 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1688,6 +1688,10 @@ ProcessUtilitySlow(ParseState *pstate, address = CreateStatistics((CreateStatsStmt *) parsetree); break; + case T_AlterStatsStmt: + address = AlterStatistics((AlterStatsStmt *) parsetree); + break; + case T_AlterCollationStmt: address = AlterCollation((AlterCollationStmt *) parsetree); break; @@ -2805,6 +2809,10 @@ CreateCommandTag(Node *parsetree) tag = "CREATE STATISTICS"; break; + case T_AlterStatsStmt: + tag = "ALTER STATISTICS"; + break; + case T_DeallocateStmt: { DeallocateStmt *stmt = (DeallocateStmt *) parsetree; @@ -3393,6 +3401,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_AlterStatsStmt: + lev = LOGSTMT_DDL; + break; + case T_AlterCollationStmt: lev = LOGSTMT_DDL; break; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 9009b05e12..35a264c29b 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1840,7 +1840,7 @@ psql_completion(const char *text, int start, int end) /* ALTER STATISTICS */ else if (Matches("ALTER", "STATISTICS", MatchAny)) - COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA"); + COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA", "SET STATISTICS"); /* ALTER TRIGGER , add ON */ else if (Matches("ALTER", "TRIGGER", MatchAny)) diff --git a/src/include/catalog/pg_statistic_ext.h b/src/include/catalog/pg_statistic_ext.h index d8c5e0651e..5f6bfdd56c 100644 --- a/src/include/catalog/pg_statistic_ext.h +++ b/src/include/catalog/pg_statistic_ext.h @@ -41,6 +41,7 @@ CATALOG(pg_statistic_ext,3381,StatisticExtRelationId) Oid stxnamespace; /* OID of statistics object's namespace */ Oid stxowner; /* statistics object's owner */ + int32 stxstattarget BKI_DEFAULT(-1); /* statistic target */ /* * variable-length fields start here, but we allow direct access to diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index b4e7db67c3..1dc6dc2ca0 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -87,6 +87,7 @@ extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void UpdateStatisticsForTypeChange(Oid statsOid, Oid relationOid, int attnum, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 4e2fb39105..57589d4fac 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -420,6 +420,7 @@ typedef enum NodeTag T_CreateStatsStmt, T_AlterCollationStmt, T_CallStmt, + T_AlterStatsStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 94ded3c135..a6a3382a46 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2789,6 +2789,18 @@ typedef struct CreateStatsStmt bool if_not_exists; /* do nothing if stats name already exists */ } CreateStatsStmt; +/* ---------------------- + * Alter Statistics Statement + * ---------------------- + */ +typedef struct AlterStatsStmt +{ + NodeTag type; + List *defnames; /* qualified name (list of Value strings) */ + int stxstattarget; /* statistics target, or NULL */ + bool if_not_exists; /* do nothing if stats name already exists */ +} AlterStatsStmt; + /* ---------------------- * Create Function Statement * ---------------------- diff --git a/src/include/statistics/extended_stats_internal.h b/src/include/statistics/extended_stats_internal.h index 8fc541993f..71fd509f16 100644 --- a/src/include/statistics/extended_stats_internal.h +++ b/src/include/statistics/extended_stats_internal.h @@ -71,7 +71,7 @@ extern MVDependencies *statext_dependencies_deserialize(bytea *data); extern MCVList *statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs, VacAttrStats **stats, - double totalrows); + double totalrows, int stattarget); extern bytea *statext_mcv_serialize(MCVList *mcv, VacAttrStats **stats); extern MCVList *statext_mcv_deserialize(bytea *data); diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h index cb7bc630e9..53ec906936 100644 --- a/src/include/statistics/statistics.h +++ b/src/include/statistics/statistics.h @@ -100,6 +100,8 @@ extern MCVList *statext_mcv_load(Oid mvoid); extern void BuildRelationExtStatistics(Relation onerel, double totalrows, int numrows, HeapTuple *rows, int natts, VacAttrStats **vacattrstats); +extern int ComputeExtStatisticsTarget(Relation onerel, + int natts, VacAttrStats **stats); extern bool statext_is_kind_built(HeapTuple htup, char kind); extern Selectivity dependencies_clauselist_selectivity(PlannerInfo *root, List *clauses,