From d06215d03b50c264a0f31e335b895ee1b6753e68 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Tue, 10 Sep 2019 20:09:27 +0200 Subject: [PATCH] Allow setting statistics target for extended statistics When building statistics, we need to decide how many rows to sample and how accurate the resulting statistics should be. Until now, it was not possible to explicitly define statistics target for extended statistics objects, the value was always computed from the per-attribute targets with a fallback to the system-wide default statistics target. That's a bit inconvenient, as it ties together the statistics target set for per-column and extended statistics. In some cases it may be useful to require larger sample / higher accuracy for extended statics (or the other way around), but with this approach that's not possible. So this commit introduces a new command, allowing to specify statistics target for individual extended statistics objects, overriding the value derived from per-attribute targets (and the system default). ALTER STATISTICS stat_name SET STATISTICS target_value; When determining statistics target for an extended statistics object we first look at this explicitly set value. When this value is -1, we fall back to the old formula, looking at the per-attribute targets first and then the system default. This means the behavior is backwards compatible with older PostgreSQL releases. Author: Tomas Vondra Discussion: https://postgr.es/m/20190618213357.vli3i23vpkset2xd@development Reviewed-by: Kirk Jamison, Dean Rasheed --- doc/src/sgml/ref/alter_statistics.sgml | 17 ++ src/backend/commands/analyze.c | 13 +- src/backend/commands/statscmds.c | 108 +++++++++++++ src/backend/nodes/copyfuncs.c | 15 ++ src/backend/nodes/equalfuncs.c | 13 ++ src/backend/nodes/outfuncs.c | 13 ++ src/backend/parser/gram.y | 31 +++- src/backend/statistics/extended_stats.c | 150 +++++++++++++++++- src/backend/statistics/mcv.c | 15 +- src/backend/tcop/utility.c | 12 ++ src/bin/pg_dump/pg_dump.c | 30 +++- src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/t/002_pg_dump.pl | 11 ++ src/bin/psql/tab-complete.c | 2 +- src/include/catalog/pg_statistic_ext.h | 1 + src/include/commands/defrem.h | 1 + src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 12 ++ .../statistics/extended_stats_internal.h | 2 +- src/include/statistics/statistics.h | 2 + src/test/regress/expected/stats_ext.out | 17 ++ src/test/regress/sql/stats_ext.sql | 10 ++ 22 files changed, 458 insertions(+), 19 deletions(-) diff --git a/doc/src/sgml/ref/alter_statistics.sgml b/doc/src/sgml/ref/alter_statistics.sgml index 58c7ed020d..be4c3f1f05 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 statistics 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 ed0d4e6d4f..7accb950eb 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -307,7 +307,8 @@ do_analyze_rel(Relation onerel, VacuumParams *params, VacAttrStats **vacattrstats; AnlIndexData *indexdata; int targrows, - numrows; + numrows, + minrows; double totalrows, totaldeadrows; HeapTuple *rows; @@ -491,6 +492,16 @@ do_analyze_rel(Relation onerel, VacuumParams *params, } } + /* + * Look at extended statistics objects too, as those may define custom + * statistics target. So we may need to sample more rows and then build + * the statistics with enough detail. + */ + minrows = ComputeExtStatisticsRows(onerel, attr_cnt, vacattrstats); + + if (targrows < minrows) + targrows = minrows; + /* * Acquire the sample rows */ diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index 34d11c2a98..f51eb7bb64 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,110 @@ 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 statistics 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 statistics object */ + stxoid = get_statistics_object_oid(stmt->defnames, stmt->missing_ok); + + /* + * If we got here and the OID is not valid, it means the statistics + * does not exist, but the command specified IF EXISTS. So report + * this as a simple NOTICE and we're done. + */ + if (!OidIsValid(stxoid)) + { + char *schemaname; + char *statname; + + Assert(stmt->missing_ok); + + DeconstructQualifiedName(stmt->defnames, &schemaname, &statname); + + if (schemaname) + ereport(NOTICE, + (errmsg("statistics object \"%s.%s\" does not exist, skipping", + schemaname, statname))); + else + ereport(NOTICE, + (errmsg("statistics object \"%s\" does not exist, skipping", + statname))); + + return InvalidObjectAddress; + } + + /* Search pg_statistic_ext */ + rel = table_open(StatisticExtRelationId, RowExclusiveLock); + + oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid)); + + /* Must be owner of the existing statistics 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 statistics 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 a2617c7cfd..3432bb921d 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3497,6 +3497,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(missing_ok); + + return newnode; +} + static CreateFunctionStmt * _copyCreateFunctionStmt(const CreateFunctionStmt *from) { @@ -5211,6 +5223,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..18cb014373 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(missing_ok); + + 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 e6ce8e2110..b0dcd02ff6 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2668,6 +2668,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(missing_ok); +} + static void _outNotifyStmt(StringInfo str, const NotifyStmt *node) { @@ -4130,6 +4140,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 a954acf509..3f67aaf30e 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->missing_ok = false; + n->stxstattarget = $6; + $$ = (Node *)n; + } + | ALTER STATISTICS IF_P EXISTS any_name SET STATISTICS SignedIconst + { + AlterStatsStmt *n = makeNode(AlterStatsStmt); + n->defnames = $5; + n->missing_ok = true; + n->stxstattarget = $8; + $$ = (Node *)n; + } + ; + /***************************************************************************** * * QUERY : diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index dd29b2f9ff..207ee3160e 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -62,6 +62,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; /* statistics target (-1 for default) */ } StatExtEntry; @@ -71,7 +72,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 @@ -107,6 +109,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 @@ -131,6 +134,19 @@ BuildRelationExtStatistics(Relation onerel, double totalrows, Assert(bms_num_members(stat->columns) >= 2 && bms_num_members(stat->columns) <= STATS_MAX_DIMENSIONS); + /* compute statistics target for this statistics */ + stattarget = statext_compute_stattarget(stat->stattarget, + bms_num_members(stat->columns), + stats); + + /* + * Don't rebuild statistics objects with statistics target set to 0 (we + * just leave the existing values around, just like we do for regular + * per-column statistics). + */ + if (stattarget == 0) + continue; + /* compute statistic of each requested type */ foreach(lc2, stat->types) { @@ -144,7 +160,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 */ @@ -157,6 +173,135 @@ BuildRelationExtStatistics(Relation onerel, double totalrows, MemoryContextDelete(cxt); } +/* + * ComputeExtStatisticsRows + * Compute number of rows required by extended statistics on a table. + * + * Computes number of rows we need to sample to build extended statistics on a + * table. This only looks at statistics we can actually build - for example + * when analyzing only some of the columns, this will skip statistics objects + * that would require additional columns. + * + * See statext_compute_stattarget for details about how we compute statistics + * target for a statistics objects (from the object target, attribute targets + * and default statistics target). + */ +int +ComputeExtStatisticsRows(Relation onerel, + int natts, VacAttrStats **vacattrstats) +{ + Relation pg_stext; + ListCell *lc; + List *lstats; + MemoryContext cxt; + MemoryContext oldcxt; + int result = 0; + + cxt = AllocSetContextCreate(CurrentMemoryContext, + "ComputeExtStatisticsRows", + 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 this statistics object based on the columns + * analyzed. If not, ignore it (don't report anything, we'll do that + * during the actual build BuildRelationExtStatistics). + */ + stats = lookup_var_attr_stats(onerel, stat->columns, + natts, vacattrstats); + + if (!stats) + continue; + + /* + * Compute statistics target, based on what's set for the statistic + * object itself, and for its attributes. + */ + stattarget = statext_compute_stattarget(stat->stattarget, + nattrs, stats); + + /* Use the largest value for all statistics objects. */ + if (stattarget > result) + result = stattarget; + } + + table_close(pg_stext, RowExclusiveLock); + + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(cxt); + + /* compute sample size based on the statistics target */ + return (300 * result); +} + +/* + * statext_compute_stattarget + * compute statistics target for an extended statistic + * + * When computing target for extended statistics objects, we consider three + * places where the target may be set - the statistics object itself, + * attributes the statistics is defined on, and then the default statistics + * target. + * + * First we look at what's set for the statistics object itself, using the + * ALTER STATISTICS ... SET STATISTICS command. If we find a valid value + * there (i.e. not -1) we're done. Otherwise we look at targets set for any + * of the attributes the statistic is defined on, and if there are columns + * with defined target, we use the maximum value. We do this mostly for + * backwards compatibility, because this is what we did before having + * statistics target for extended statistics. + * + * And finally, if we still don't have a statistics target, we use the value + * set in default_statistics_target. + */ +static int +statext_compute_stattarget(int stattarget, int nattrs, VacAttrStats **stats) +{ + int i; + + /* + * If there's statistics target set for the statistics object, use it. + * It may be set to 0 which disables building of that statistic. + */ + if (stattarget >= 0) + return stattarget; + + /* + * The target for the statistics object is set to -1, in which case we + * look at the maximum target set for any of the attributes the object + * is defined on. + */ + for (i = 0; i < nattrs; i++) + { + /* keep the maximmum statistics target */ + if (stats[i]->attr->attstattarget > stattarget) + stattarget = stats[i]->attr->attstattarget; + } + + /* + * If the value is still negative (so neither the statistics object nor + * any of the columns have custom statistics target set), use the global + * default target. + */ + if (stattarget < 0) + stattarget = default_statistics_target; + + /* As this point we should have a valid statistics 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? @@ -225,6 +370,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 bae6f21968..ee35f6afc5 100644 --- a/src/backend/statistics/mcv.c +++ b/src/backend/statistics/mcv.c @@ -181,7 +181,7 @@ 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, @@ -210,15 +210,12 @@ statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs, groups = build_distinct_groups(nitems, items, mss, &ngroups); /* - * Maximum number of MCV items to store, based on the attribute with the - * largest stats target (and the number of groups we have available). + * Maximum number of MCV items to store, based on the statistics target + * we computed for the statistics object (from target set for the object + * itself, attributes and the system default). In any case, we can't keep + * more groups than 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 7f6f0b6498..c6faa6619d 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/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 7ec0c84540..f01fea5b91 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -7178,6 +7178,7 @@ getExtendedStatistics(Archive *fout) int i_stxname; int i_stxnamespace; int i_rolname; + int i_stattarget; int i; /* Extended statistics were new in v10 */ @@ -7186,10 +7187,16 @@ getExtendedStatistics(Archive *fout) query = createPQExpBuffer(); - appendPQExpBuffer(query, "SELECT tableoid, oid, stxname, " - "stxnamespace, (%s stxowner) AS rolname " - "FROM pg_catalog.pg_statistic_ext", - username_subquery); + if (fout->remoteVersion < 130000) + appendPQExpBuffer(query, "SELECT tableoid, oid, stxname, " + "stxnamespace, (%s stxowner) AS rolname, (-1) AS stxstattarget " + "FROM pg_catalog.pg_statistic_ext", + username_subquery); + else + appendPQExpBuffer(query, "SELECT tableoid, oid, stxname, " + "stxnamespace, (%s stxowner) AS rolname, stxstattarget " + "FROM pg_catalog.pg_statistic_ext", + username_subquery); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -7200,6 +7207,7 @@ getExtendedStatistics(Archive *fout) i_stxname = PQfnumber(res, "stxname"); i_stxnamespace = PQfnumber(res, "stxnamespace"); i_rolname = PQfnumber(res, "rolname"); + i_stattarget = PQfnumber(res, "stxstattarget"); statsextinfo = (StatsExtInfo *) pg_malloc(ntups * sizeof(StatsExtInfo)); @@ -7214,6 +7222,7 @@ getExtendedStatistics(Archive *fout) findNamespace(fout, atooid(PQgetvalue(res, i, i_stxnamespace))); statsextinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); + statsextinfo[i].stattarget = atoi(PQgetvalue(res, i, i_stattarget)); /* Decide whether we want to dump it */ selectDumpableObject(&(statsextinfo[i].dobj), fout); @@ -16501,6 +16510,19 @@ dumpStatisticsExt(Archive *fout, StatsExtInfo *statsextinfo) /* Result of pg_get_statisticsobjdef is complete except for semicolon */ appendPQExpBuffer(q, "%s;\n", stxdef); + /* + * We only issue an ALTER STATISTICS statement if the stxstattarget entry + * for this statictics object is non-negative (i.e. it's not the default + * value). + */ + if (statsextinfo->stattarget >= 0) + { + appendPQExpBuffer(q, "ALTER STATISTICS %s ", + fmtQualifiedDumpable(statsextinfo)); + appendPQExpBuffer(q, "SET STATISTICS %d;\n", + statsextinfo->stattarget); + } + appendPQExpBuffer(delq, "DROP STATISTICS %s;\n", fmtQualifiedDumpable(statsextinfo)); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index dfba58ac58..ec5a924b8f 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -382,6 +382,7 @@ typedef struct _statsExtInfo { DumpableObject dobj; char *rolname; /* name of owner, or empty string */ + int stattarget; /* statistics target */ } StatsExtInfo; typedef struct _ruleInfo diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index f064ea1063..4712e3a958 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2542,6 +2542,17 @@ my %tests = ( unlike => { exclude_dump_test_schema => 1, }, }, + 'ALTER STATISTICS extended_stats_options' => { + create_order => 98, + create_sql => 'ALTER STATISTICS dump_test.test_ext_stats_opts SET STATISTICS 1000', + regexp => qr/^ + \QALTER STATISTICS dump_test.test_ext_stats_opts SET STATISTICS 1000;\E + /xms, + like => + { %full_runs, %dump_test_schema_runs, section_post_data => 1, }, + unlike => { exclude_dump_test_schema => 1, }, + }, + 'CREATE SEQUENCE test_table_col1_seq' => { regexp => qr/^ \QCREATE SEQUENCE dump_test.test_table_col1_seq\E diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index bcc7404c55..890fc5322d 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..54a88f4ec8 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); /* statistics 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 3cbb08df92..bce2d59b0d 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..f21ff8028a 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 */ + bool missing_ok; /* skip error if statistics object is missing */ +} AlterStatsStmt; + /* ---------------------- * Create Function Statement * ---------------------- diff --git a/src/include/statistics/extended_stats_internal.h b/src/include/statistics/extended_stats_internal.h index 8433c34f6d..fcf4e8ae0b 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 3b7da3be60..588b6738b2 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 ComputeExtStatisticsRows(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, diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index d782ebcfea..e56c75cd33 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -98,11 +98,28 @@ CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; ANALYZE ab1; WARNING: statistics object "public.ab1_a_b_stats" could not be computed for relation "public.ab1" ALTER TABLE ab1 ALTER a SET STATISTICS -1; +-- setting statistics target 0 skips the statistics, without printing any message, so check catalog +ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +ANALYZE ab1; +SELECT stxname, stxdndistinct, stxddependencies, stxdmcv + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxname = 'ab1_a_b_stats' + AND d.stxoid = s.oid; + stxname | stxdndistinct | stxddependencies | stxdmcv +---------------+---------------+------------------+--------- + ab1_a_b_stats | | | +(1 row) + +ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1; -- partial analyze doesn't build stats either ANALYZE ab1 (a); WARNING: statistics object "public.ab1_a_b_stats" could not be computed for relation "public.ab1" ANALYZE ab1; DROP TABLE ab1; +ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +ERROR: statistics object "ab1_a_b_stats" does not exist +ALTER STATISTICS IF EXISTS ab1_a_b_stats SET STATISTICS 0; +NOTICE: statistics object "ab1_a_b_stats" does not exist, skipping -- Ensure we can build statistics for tables with inheritance. CREATE TABLE ab1 (a INTEGER, b INTEGER); CREATE TABLE ab1c () INHERITS (ab1); diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index d506e8238c..65d1e39f0c 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -68,10 +68,20 @@ INSERT INTO ab1 SELECT a, a%23 FROM generate_series(1, 1000) a; CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; ANALYZE ab1; ALTER TABLE ab1 ALTER a SET STATISTICS -1; +-- setting statistics target 0 skips the statistics, without printing any message, so check catalog +ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +ANALYZE ab1; +SELECT stxname, stxdndistinct, stxddependencies, stxdmcv + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxname = 'ab1_a_b_stats' + AND d.stxoid = s.oid; +ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1; -- partial analyze doesn't build stats either ANALYZE ab1 (a); ANALYZE ab1; DROP TABLE ab1; +ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +ALTER STATISTICS IF EXISTS ab1_a_b_stats SET STATISTICS 0; -- Ensure we can build statistics for tables with inheritance. CREATE TABLE ab1 (a INTEGER, b INTEGER);