]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add pg_restore_extended_stats()
authorMichael Paquier <michael@paquier.xyz>
Mon, 26 Jan 2026 06:08:15 +0000 (15:08 +0900)
committerMichael Paquier <michael@paquier.xyz>
Mon, 26 Jan 2026 06:08:15 +0000 (15:08 +0900)
This function closely mirror its relation and attribute counterparts,
but for extended statistics (i.e. CREATE STATISTICS) objects, being
able to restore extended statistics for an extended stats object.  Like
the other functions, the goal of this feature is to ease the dump or
upgrade of clusters so as ANALYZE would not be required anymore after
these operations, stats being directly loaded into the target cluster
without any post-dump/upgrade computation.

The caller of this function needs the following arguments for the
extended stats to restore:
- The name of the relation.
- The schema name of the relation.
- The name of the extended stats object.
- The schema name of the extended stats object.
- If the stats are inherited or not.
- One or more extended stats kind with its data.

This commit adds only support for the restore of the extended statistics
kind "n_distinct", building the basic infrastructure for the restore
of more extended statistics kinds in follow-up commits, including MVC
and dependencies.

The support for "n_distinct" is eased in this commit thanks to the
previous work done particularly in commits 1f927cce4498 and
44eba8f06e55, that have added the input function for the type
pg_ndistinct, used as data type in input of this new restore function.

Bump catalog version.

Author: Corey Huinker <corey.huinker@gmail.com>
Co-authored-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/CADkLM=dpz3KFnqP-dgJ-zvRvtjsa8UZv8wDAQdqho=qN3kX0Zg@mail.gmail.com

doc/src/sgml/func/func-admin.sgml
src/backend/statistics/extended_stats_funcs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/test/regress/expected/stats_import.out
src/test/regress/sql/stats_import.sql
src/tools/pgindent/typedefs.list

index e7ea16f73b31eb156c27ce51cdf3bac558e36d98..3800cf3da09b65b147b858c9d2a9d4e87d6ee41c 100644 (file)
@@ -2165,6 +2165,85 @@ SELECT pg_restore_attribute_stats(
         </para>
        </entry>
       </row>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_restore_extended_stats</primary>
+        </indexterm>
+        <function>pg_restore_extended_stats</function> (
+        <literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
+        <returnvalue>boolean</returnvalue>
+        </para>
+        <para>
+         Creates or updates statistics for statistics objects.  Ordinarily,
+         these statistics are collected automatically or updated as a part of
+         <xref linkend="sql-vacuum"/> or <xref linkend="sql-analyze"/>, so
+         it's not necessary to call this function.  However, it is useful
+         after a restore to enable the optimizer to choose better plans if
+         <command>ANALYZE</command> has not been run yet.
+        </para>
+        <para>
+         The tracked statistics may change from version to version, so
+         arguments are passed as pairs of <replaceable>argname</replaceable>
+         and <replaceable>argvalue</replaceable> in the form:
+<programlisting>
+ SELECT pg_restore_extended_stats(
+    '<replaceable>arg1name</replaceable>', '<replaceable>arg1value</replaceable>'::<replaceable>arg1type</replaceable>,
+    '<replaceable>arg2name</replaceable>', '<replaceable>arg2value</replaceable>'::<replaceable>arg2type</replaceable>,
+    '<replaceable>arg3name</replaceable>', '<replaceable>arg3value</replaceable>'::<replaceable>arg3type</replaceable>);
+</programlisting>
+        </para>
+        <para>
+         For example, to set the <structfield>n_distinct</structfield>,
+         <structfield>dependencies</structfield>, and <structfield>exprs</structfield>
+         values for the statistics object <structname>myschema.mystatsobj</structname>:
+<programlisting>
+ SELECT pg_restore_extended_stats(
+    'schemaname',            'tab_schema'::name,
+    'relname',               'tab_name'::name,
+    'statistics_schemaname', 'stats_schema'::name,
+    'statistics_name',       'stats_name'::name,
+    'inherited',             false,
+    'n_distinct',            '[{"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct);
+</programlisting>
+        </para>
+        <para>
+         The required arguments are <literal>schemaname</literal> with a value
+         of type <type>name</type>, for the schema of the table to which the
+         statistics are related to, <literal>relname</literal> with a value
+         of type <type>name</type>, for the table to which the statistics are
+         related to, <literal>statistics_schemaname</literal>
+         with a value of type <type>name</type>, which specifies the statistics
+         object's schema, <literal>statistics_name</literal> with a value of
+         type <type>name</type>, which specifies the name of the statistics
+         object and <literal>inherited</literal>, which specifies whether
+         the statistics include values from child tables.
+        </para>
+        <para>
+         Other arguments are the names and values of statistics corresponding
+         to columns in <link linkend="view-pg-stats-ext"><structname>pg_stats_ext</structname>
+         </link>.
+         This function currently supports <literal>n_distinct</literal>.
+        </para>
+        <para>
+         Additionally, this function accepts argument name
+         <literal>version</literal> of type <type>integer</type>, which
+         specifies the server version from which the statistics originated.
+         This is anticipated to be helpful in porting statistics from older
+         versions of <productname>PostgreSQL</productname>.
+        </para>
+        <para>
+         Minor errors are reported as a <literal>WARNING</literal> and
+         ignored, and remaining statistics will still be restored. If all
+         specified statistics are successfully restored, returns
+         <literal>true</literal>, otherwise <literal>false</literal>.
+        </para>
+        <para>
+         The caller must have the <literal>MAINTAIN</literal> privilege on the
+         table or be the owner of the database.
+        </para>
+       </entry>
+      </row>
       <row>
        <entry role="func_table_entry">
         <para role="func_signature">
index b4b1bf26463a9cc03bc2a4759460736ee83a805c..269fdabdfc0ea029ee98102ccd7828836ad44609 100644 (file)
 #include "catalog/pg_statistic_ext_data.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "statistics/extended_stats_internal.h"
 #include "statistics/stat_utils.h"
 #include "utils/acl.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -42,6 +44,7 @@ enum extended_stats_argnum
        STATSCHEMA_ARG,
        STATNAME_ARG,
        INHERITED_ARG,
+       NDISTINCT_ARG,
        NUM_EXTENDED_STATS_ARGS,
 };
 
@@ -56,13 +59,33 @@ static struct StatsArgInfo extarginfo[] =
        [STATSCHEMA_ARG] = {"statistics_schemaname", TEXTOID},
        [STATNAME_ARG] = {"statistics_name", TEXTOID},
        [INHERITED_ARG] = {"inherited", BOOLOID},
+       [NDISTINCT_ARG] = {"n_distinct", PG_NDISTINCTOID},
        [NUM_EXTENDED_STATS_ARGS] = {0},
 };
 
+static bool extended_statistics_update(FunctionCallInfo fcinfo);
+
 static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
                                                                          const char *stxname);
 static bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited);
 
+/*
+ * Track the extended statistics kinds expected for a pg_statistic_ext
+ * tuple.
+ */
+typedef struct
+{
+       bool            ndistinct;
+       bool            dependencies;
+       bool            mcv;
+       bool            expressions;
+} StakindFlags;
+
+static void expand_stxkind(HeapTuple tup, StakindFlags *enabled);
+static void upsert_pg_statistic_ext_data(const Datum *values,
+                                                                                const bool *nulls,
+                                                                                const bool *replaces);
+
 /*
  * Fetch a pg_statistic_ext row by name and namespace OID.
  */
@@ -109,6 +132,278 @@ get_pg_statistic_ext(Relation pg_stext, Oid nspoid, const char *stxname)
        return SearchSysCacheCopy1(STATEXTOID, ObjectIdGetDatum(stxoid));
 }
 
+/*
+ * Decode the stxkind column so that we know which stats types to expect,
+ * returning a StakindFlags set depending on the stats kinds expected by
+ * a pg_statistic_ext tuple.
+ */
+static void
+expand_stxkind(HeapTuple tup, StakindFlags *enabled)
+{
+       Datum           datum;
+       ArrayType  *arr;
+       char       *kinds;
+
+       datum = SysCacheGetAttrNotNull(STATEXTOID,
+                                                                  tup,
+                                                                  Anum_pg_statistic_ext_stxkind);
+       arr = DatumGetArrayTypeP(datum);
+       if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID)
+               elog(ERROR, "stxkind is not a one-dimension char array");
+
+       kinds = (char *) ARR_DATA_PTR(arr);
+
+       for (int i = 0; i < ARR_DIMS(arr)[0]; i++)
+       {
+               switch (kinds[i])
+               {
+                       case STATS_EXT_NDISTINCT:
+                               enabled->ndistinct = true;
+                               break;
+                       case STATS_EXT_DEPENDENCIES:
+                               enabled->dependencies = true;
+                               break;
+                       case STATS_EXT_MCV:
+                               enabled->mcv = true;
+                               break;
+                       case STATS_EXT_EXPRESSIONS:
+                               enabled->expressions = true;
+                               break;
+                       default:
+                               elog(ERROR, "incorrect stxkind %c found", kinds[i]);
+                               break;
+               }
+       }
+}
+
+/*
+ * Perform the actual storage of a pg_statistic_ext_data tuple.
+ */
+static void
+upsert_pg_statistic_ext_data(const Datum *values, const bool *nulls,
+                                                        const bool *replaces)
+{
+       Relation        pg_stextdata;
+       HeapTuple       stxdtup;
+       HeapTuple       newtup;
+
+       pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+
+       stxdtup = SearchSysCache2(STATEXTDATASTXOID,
+                                                         values[Anum_pg_statistic_ext_data_stxoid - 1],
+                                                         values[Anum_pg_statistic_ext_data_stxdinherit - 1]);
+
+       if (HeapTupleIsValid(stxdtup))
+       {
+               newtup = heap_modify_tuple(stxdtup,
+                                                                  RelationGetDescr(pg_stextdata),
+                                                                  values,
+                                                                  nulls,
+                                                                  replaces);
+               CatalogTupleUpdate(pg_stextdata, &newtup->t_self, newtup);
+               ReleaseSysCache(stxdtup);
+       }
+       else
+       {
+               newtup = heap_form_tuple(RelationGetDescr(pg_stextdata), values, nulls);
+               CatalogTupleInsert(pg_stextdata, newtup);
+       }
+
+       heap_freetuple(newtup);
+
+       CommandCounterIncrement();
+
+       table_close(pg_stextdata, RowExclusiveLock);
+}
+
+/*
+ * Insert or update an extended statistics object.
+ *
+ * Major errors, such as the table not existing or permission errors, are
+ * reported as ERRORs.  There are a couple of paths that generate a WARNING,
+ * like when the statistics object or its schema do not exist, a conversion
+ * failure on one statistic kind, or when other statistic kinds may still
+ * be updated.
+ */
+static bool
+extended_statistics_update(FunctionCallInfo fcinfo)
+{
+       char       *relnspname;
+       char       *relname;
+       Oid                     nspoid;
+       char       *nspname;
+       char       *stxname;
+       bool            inherited;
+       Relation        pg_stext = NULL;
+       HeapTuple       tup = NULL;
+
+       StakindFlags enabled = {false, false, false, false};
+       StakindFlags has = {false, false, false, false};
+
+       Form_pg_statistic_ext stxform;
+
+       Datum           values[Natts_pg_statistic_ext_data] = {0};
+       bool            nulls[Natts_pg_statistic_ext_data];
+       bool            replaces[Natts_pg_statistic_ext_data] = {0};
+       bool            success = true;
+       int                     numexprs = 0;
+
+       /* arrays of type info, if we need them */
+       Oid                     relid;
+       Oid                     locked_table = InvalidOid;
+
+       /*
+        * Fill out the StakindFlags "has" structure based on which parameters
+        * were provided to the function.
+        */
+       has.ndistinct = !PG_ARGISNULL(NDISTINCT_ARG);
+
+       if (RecoveryInProgress())
+       {
+               ereport(WARNING,
+                               errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                               errmsg("recovery is in progress"),
+                               errhint("Statistics cannot be modified during recovery."));
+               return false;
+       }
+
+       /* relation arguments */
+       stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
+       relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+       stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
+       relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+
+       /* extended statistics arguments */
+       stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
+       nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
+       stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
+       stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
+       stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
+       inherited = PG_GETARG_BOOL(INHERITED_ARG);
+
+       /*
+        * First open the relation where we expect to find the statistics.  This
+        * is similar to relation and attribute statistics, so as ACL checks are
+        * done before any locks are taken, even before any attempts related to
+        * the extended stats object.
+        */
+       relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
+                                                                        ShareUpdateExclusiveLock, 0,
+                                                                        RangeVarCallbackForStats, &locked_table);
+
+       nspoid = get_namespace_oid(nspname, true);
+       if (nspoid == InvalidOid)
+       {
+               ereport(WARNING,
+                               errcode(ERRCODE_UNDEFINED_OBJECT),
+                               errmsg("could not find schema \"%s\"", nspname));
+               success = false;
+               goto cleanup;
+       }
+
+       pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
+       tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
+
+       if (!HeapTupleIsValid(tup))
+       {
+               ereport(WARNING,
+                               errcode(ERRCODE_UNDEFINED_OBJECT),
+                               errmsg("could not find extended statistics object \"%s\".\"%s\"",
+                                          quote_identifier(nspname),
+                                          quote_identifier(stxname)));
+               success = false;
+               goto cleanup;
+       }
+
+       stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
+
+       /*
+        * The relation tracked by the stats object has to match with the relation
+        * we have already locked.
+        */
+       if (stxform->stxrelid != relid)
+       {
+               ereport(WARNING,
+                               errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                               errmsg("could not restore extended statistics object \"%s\".\"%s\": incorrect relation \"%s\".\"%s\" specified",
+                                          quote_identifier(nspname),
+                                          quote_identifier(stxname),
+                                          quote_identifier(relnspname),
+                                          quote_identifier(relname)));
+
+               success = false;
+               goto cleanup;
+       }
+
+       /* Find out what extended statistics kinds we should expect. */
+       expand_stxkind(tup, &enabled);
+
+       /*
+        * If the object cannot support ndistinct, we should not have data for it.
+        */
+       if (has.ndistinct && !enabled.ndistinct)
+       {
+               ereport(WARNING,
+                               errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                               errmsg("cannot not specify parameter \"%s\"",
+                                          extarginfo[NDISTINCT_ARG].argname),
+                               errhint("Extended statistics object \"%s\".\"%s\" does not support statistics of this type.",
+                                               quote_identifier(nspname),
+                                               quote_identifier(stxname)));
+
+               has.ndistinct = false;
+               success = false;
+       }
+
+       /*
+        * Populate the pg_statistic_ext_data result tuple.
+        */
+
+       /* Primary Key: cannot be NULL or replaced. */
+       values[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(stxform->oid);
+       values[Anum_pg_statistic_ext_data_stxdinherit - 1] = BoolGetDatum(inherited);
+
+       /* All unspecified parameters will be left unmodified */
+       nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+       nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
+       nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
+       nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
+
+       /*
+        * For each stats kind, deserialize the data at hand and perform a round
+        * of validation.  The resulting tuple is filled with a set of updated
+        * values.
+        */
+
+       if (has.ndistinct)
+       {
+               Datum           ndistinct_datum = PG_GETARG_DATUM(NDISTINCT_ARG);
+               bytea      *data = DatumGetByteaPP(ndistinct_datum);
+               MVNDistinct *ndistinct = statext_ndistinct_deserialize(data);
+
+               if (statext_ndistinct_validate(ndistinct, &stxform->stxkeys,
+                                                                          numexprs, WARNING))
+               {
+                       values[Anum_pg_statistic_ext_data_stxdndistinct - 1] = ndistinct_datum;
+                       nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = false;
+                       replaces[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+               }
+               else
+                       success = false;
+
+               statext_ndistinct_free(ndistinct);
+       }
+
+       upsert_pg_statistic_ext_data(values, nulls, replaces);
+
+cleanup:
+       if (HeapTupleIsValid(tup))
+               heap_freetuple(tup);
+       if (pg_stext != NULL)
+               table_close(pg_stext, RowExclusiveLock);
+       return success;
+}
+
 /*
  * Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext
  * row and "inherited" pair.
@@ -139,6 +434,31 @@ delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
        return result;
 }
 
+/*
+ * Restore (insert or replace) statistics for the given statistics object.
+ *
+ * This function accepts variadic arguments in key-value pairs, which are
+ * given to stats_fill_fcinfo_from_arg_pairs to be mapped into positional
+ * arguments.
+ */
+Datum
+pg_restore_extended_stats(PG_FUNCTION_ARGS)
+{
+       LOCAL_FCINFO(positional_fcinfo, NUM_EXTENDED_STATS_ARGS);
+       bool            result = true;
+
+       InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_EXTENDED_STATS_ARGS,
+                                                        InvalidOid, NULL, NULL);
+
+       if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, extarginfo))
+               result = false;
+
+       if (!extended_statistics_update(positional_fcinfo))
+               result = false;
+
+       PG_RETURN_BOOL(result);
+}
+
 /*
  * Delete statistics for the given statistics object.
  */
index 79db87316211f08e3d05435ec5b2e71cae793f1d..fb5770266663e64127e42030f5e1a1d4ec0863ff 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202601221
+#define CATALOG_VERSION_NO     202601261
 
 #endif
index 894b6a1b6d6b95a9862cdb9c3d428fdd5270a17e..5e5e33f64fcb9ccab01133b55ad16e88c049cb4f 100644 (file)
   proargtypes => 'int4', prosrc => 'gist_translate_cmptype_common' },
 
 # Extended Statistics functions
+{ oid => '9947', descr => 'restore statistics on extended statistics object',
+  proname => 'pg_restore_extended_stats', provariadic => 'any',
+  proisstrict => 'f', provolatile => 'v', proparallel => 'u',
+  prorettype => 'bool', proargtypes => 'any', proargmodes => '{v}',
+  proargnames => '{kwargs}', prosrc => 'pg_restore_extended_stats' },
 { oid => '9948', descr => 'clear statistics on extended statistics object',
   proname => 'pg_clear_extended_stats', proisstrict => 'f', provolatile => 'v',
   proparallel => 'u', prorettype => 'void', proargtypes => 'text text text text bool',
index 3fd77879e1327a1ae9d4286de1acaef09c6dd2ac..acab1367855abff84bd7dd2a6006ecd71bf3effa 100644 (file)
@@ -1141,6 +1141,12 @@ CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
 CREATE STATISTICS stats_import.test_stat
   ON name, comp, lower(arange), array_length(tags,1)
   FROM stats_import.test;
+CREATE STATISTICS stats_import.test_stat_ndistinct (ndistinct)
+  ON name, comp
+  FROM stats_import.test;
+CREATE STATISTICS stats_import.test_stat_dependencies (dependencies)
+  ON name, comp
+  FROM stats_import.test;
 -- Generate statistics on table with data
 ANALYZE stats_import.test;
 CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
@@ -1590,6 +1596,147 @@ RESET ROLE;
 REVOKE MAINTAIN ON stats_import.test FROM regress_test_extstat_clear;
 REVOKE ALL ON SCHEMA stats_import FROM regress_test_extstat_clear;
 DROP ROLE regress_test_extstat_clear;
+-- Tests for pg_restore_extended_stats().
+--  Invalid argument values.
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', NULL,
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  argument "schemaname" must not be null
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', NULL,
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  argument "relname" must not be null
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', NULL,
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  argument "statistics_schemaname" must not be null
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', NULL,
+  'inherited', false);
+ERROR:  argument "statistics_name" must not be null
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', NULL);
+ERROR:  argument "inherited" must not be null
+-- Missing objects
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'schema_not_exist',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  schema "schema_not_exist" does not exist
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'table_not_exist',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  relation "stats_import.table_not_exist" does not exist
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'schema_not_exist',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+WARNING:  could not find schema "schema_not_exist"
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'ext_stats_not_exist',
+  'inherited', false);
+WARNING:  could not find extended statistics object "stats_import"."ext_stats_not_exist"
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- Incorrect relation/extended stats combination
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+WARNING:  could not restore extended statistics object "stats_import"."test_stat_clone": incorrect relation "stats_import"."test" specified
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- ndistinct value doesn't match object definition
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_ndistinct',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct);
+WARNING:  could not validate "pg_ndistinct" object: invalid attribute number 1 found
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- Incorrect extended stats kind, ndistinct not supported
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_dependencies',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct);
+WARNING:  cannot not specify parameter "n_distinct"
+HINT:  Extended statistics object "stats_import"."test_stat_dependencies" does not support statistics of this type.
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- ok: ndistinct
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_ndistinct',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct);
+ pg_restore_extended_stats 
+---------------------------
+ t
+(1 row)
+
+SELECT replace(e.n_distinct,   '}, ', E'},\n') AS n_distinct
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import' AND
+    e.statistics_name = 'test_stat_ndistinct' AND
+    e.inherited = false;
+                n_distinct                
+------------------------------------------
+ [{"attributes": [2, 3], "ndistinct": 4}]
+(1 row)
+
 DROP SCHEMA stats_import CASCADE;
 NOTICE:  drop cascades to 7 other objects
 DETAIL:  drop cascades to type stats_import.complex_type
index c2d927203d58813a646cd78191f3978515c07f47..5d35de1bc885fd707464150a17c4b77ac61131e3 100644 (file)
@@ -811,6 +811,14 @@ CREATE STATISTICS stats_import.test_stat
   ON name, comp, lower(arange), array_length(tags,1)
   FROM stats_import.test;
 
+CREATE STATISTICS stats_import.test_stat_ndistinct (ndistinct)
+  ON name, comp
+  FROM stats_import.test;
+
+CREATE STATISTICS stats_import.test_stat_dependencies (dependencies)
+  ON name, comp
+  FROM stats_import.test;
+
 -- Generate statistics on table with data
 ANALYZE stats_import.test;
 
@@ -1137,4 +1145,101 @@ REVOKE MAINTAIN ON stats_import.test FROM regress_test_extstat_clear;
 REVOKE ALL ON SCHEMA stats_import FROM regress_test_extstat_clear;
 DROP ROLE regress_test_extstat_clear;
 
+-- Tests for pg_restore_extended_stats().
+--  Invalid argument values.
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', NULL,
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', NULL,
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', NULL,
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', NULL,
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', NULL);
+-- Missing objects
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'schema_not_exist',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'table_not_exist',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'schema_not_exist',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'ext_stats_not_exist',
+  'inherited', false);
+-- Incorrect relation/extended stats combination
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+
+-- ndistinct value doesn't match object definition
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_ndistinct',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct);
+-- Incorrect extended stats kind, ndistinct not supported
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_dependencies',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct);
+
+-- ok: ndistinct
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_ndistinct',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct);
+
+SELECT replace(e.n_distinct,   '}, ', E'},\n') AS n_distinct
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import' AND
+    e.statistics_name = 'test_stat_ndistinct' AND
+    e.inherited = false;
+
 DROP SCHEMA stats_import CASCADE;
index 1c8610fd46c1c0a49e912f37ffedbe84a2161a85..ddbe4c64971f5ef6c2680436dab5d3efa5d2bf3d 100644 (file)
@@ -2897,6 +2897,7 @@ SplitPoint
 SplitTextOutputData
 SplitVar
 StackElem
+StakindFlags
 StartDataPtrType
 StartLOPtrType
 StartLOsPtrType