Allow setting statistics target for extended statistics
authorTomas Vondra <tomas.vondra@postgresql.org>
Tue, 10 Sep 2019 18:09:27 +0000 (20:09 +0200)
committerTomas Vondra <tomas.vondra@postgresql.org>
Tue, 10 Sep 2019 22:25:51 +0000 (00:25 +0200)
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
22 files changed:
doc/src/sgml/ref/alter_statistics.sgml
src/backend/commands/analyze.c
src/backend/commands/statscmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/statistics/extended_stats.c
src/backend/statistics/mcv.c
src/backend/tcop/utility.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/pg_dump/t/002_pg_dump.pl
src/bin/psql/tab-complete.c
src/include/catalog/pg_statistic_ext.h
src/include/commands/defrem.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/statistics/extended_stats_internal.h
src/include/statistics/statistics.h
src/test/regress/expected/stats_ext.out
src/test/regress/sql/stats_ext.sql

index 58c7ed020ddea6309193bcac2dbd1f162d927dba..be4c3f1f0576e73de7ab90012590a7e0ba6b1abf 100644 (file)
@@ -26,6 +26,7 @@ PostgreSQL documentation
 ALTER STATISTICS <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
 ALTER STATISTICS <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
 ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET STATISTICS <replaceable class="parameter">new_target</replaceable>
 </synopsis>
  </refsynopsisdiv>
 
@@ -93,6 +94,22 @@ ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <r
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="parameter">new_target</replaceable></term>
+      <listitem>
+       <para>
+        The statistic-gathering target for this statistics object for subsequent
+        <xref linkend="sql-analyze"/> 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 (<xref linkend="guc-default-statistics-target"/>).
+        For more information on the use of statistics by the
+        <productname>PostgreSQL</productname> query planner, refer to
+        <xref linkend="planner-stats"/>.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
   </refsect1>
index ed0d4e6d4ff67fb2f2e3d46c232a374716e59b72..7accb950eb1bda314c88fe9b2da8bd5c6a50b877 100644 (file)
@@ -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
     */
index 34d11c2a9802675ae8df6bfabe0a51e80e813f38..f51eb7bb64e196ecb36f9ec873eec6a863fd13e6 100644 (file)
@@ -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.
  */
index a2617c7cfd3c520ffe561fd89c1df7b27d35b1ad..3432bb921dd2d116623c55929ca76d1209e0876f 100644 (file)
@@ -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;
index 4f2ebe5118e97f15b3f7b3bb19d51b284ed0e6aa..18cb0143733c026ce7ac6a28f5bfad9fa6afe1b4 100644 (file)
@@ -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;
index e6ce8e2110178aebf89ef4c9a40a90097015b65f..b0dcd02ff68493864fdf1723d420665d636a13e7 100644 (file)
@@ -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;
index a954acf509e5bda7a4594fbe95049d33649fa1e6..3f67aaf30eae9ff9c104f9a7a0e5df206d272e84 100644 (file)
@@ -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  <SignedIconst>
+ *
+ *****************************************************************************/
+
+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 :
index dd29b2f9fff730a1aad3b4fce39623b8d2138efa..207ee3160ef2547debe165d0967d8956ed42c91a 100644 (file)
@@ -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,
index bae6f219684280d197f49d42dd840d08368c03b8..ee35f6afc56b84b0538fa1c9dc7ee02c957ad20c 100644 (file)
@@ -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;
 
index 7f6f0b6498f2df170d106c7716ad7fbab2ac0657..c6faa6619d2fc2b7d87347ceba97e5c6b8909c22 100644 (file)
@@ -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;
index 7ec0c84540dfa5e91ee48ac1c851468ace0f8392..f01fea5b91339402c372a8d5a66bdd6b4314da37 100644 (file)
@@ -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));
 
index dfba58ac5877b11054da4a5527c3b1786458f851..ec5a924b8fa664607677bf77b0fd9b34e5c9b5ad 100644 (file)
@@ -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
index f064ea10633f2a1cc9ba27558f82fb1d6aa4d152..4712e3a95812c193ca210e57c7f7bfb531c8a118 100644 (file)
@@ -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
index bcc7404c55098913a4fb2a948e96023c500dbf8e..890fc5322d0b8873a7e24d34f555c1fe6183d25e 100644 (file)
@@ -1840,7 +1840,7 @@ psql_completion(const char *text, int start, int end)
 
    /* ALTER STATISTICS <name> */
    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 <name>, add ON */
    else if (Matches("ALTER", "TRIGGER", MatchAny))
index d8c5e0651ec70af4c82a6f0eaea3b3375ab03f70..54a88f4ec878a839345e867899e033455fe36207 100644 (file)
@@ -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
index b4e7db67c3fa1b6ea5a56f470f39596dc3eeb549..1dc6dc2ca046cc7d9f8fed80c77ed9d6917e73fe 100644 (file)
@@ -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,
index 3cbb08df92a05150224bfdde7d9f77136dae0b51..bce2d59b0dbcf871289e396f66c9e2c5295f2a1a 100644 (file)
@@ -420,6 +420,7 @@ typedef enum NodeTag
    T_CreateStatsStmt,
    T_AlterCollationStmt,
    T_CallStmt,
+   T_AlterStatsStmt,
 
    /*
     * TAGS FOR PARSE TREE NODES (parsenodes.h)
index 94ded3c135ed8efea3b417820607bfeceb10ce22..f21ff8028a1924e7005347a5fc0027188ad8e5c8 100644 (file)
@@ -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
  * ----------------------
index 8433c34f6d7ddc55302f5ffc78ad53750fbfe459..fcf4e8ae0b3d44cf20b58f18f63ddc9cfb4cb76c 100644 (file)
@@ -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);
 
index 3b7da3be6094fd3b772430c4293a2e3e59ced046..588b6738b2a686e87629133dd17dabe30c8e8b8f 100644 (file)
@@ -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,
index d782ebcfeaa8629eba90dbfaf4867a5552cc3e0b..e56c75cd33b4c324ba94a73dce87448ad7510cd9 100644 (file)
@@ -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);
index d506e8238c32efd86b8d5053bd494132ef956c28..65d1e39f0c21b91a7f9536a31111226c1fc17f0f 100644 (file)
@@ -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);