public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH] Add new GUC compression_algorithm
87+ messages / 13 participants
[nested] [flat]

* [PATCH] Add new GUC compression_algorithm
@ 2019-08-04 00:02 Petr Jelinek <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Petr Jelinek @ 2019-08-04 00:02 UTC (permalink / raw)

Sets which algorithm to use for TOAST and WAL compression.

Currently allows either pglz which is the standard PostgreSQL algorithm
or lz4 if the PostgreSQL was configured with --with-lz4 (which is the
default).

The implementation allows different values to have different compression
algorithms and also supports reading old TOAST format which always uses
the pglz.
---
 configure                               | 116 ++++++++++++++++++--
 configure.in                            |  19 ++++
 doc/src/sgml/config.sgml                |  38 +++++++
 doc/src/sgml/storage.sgml               |   5 +-
 src/Makefile.global.in                  |   1 +
 src/backend/access/heap/tuptoaster.c    |  86 +++++++++++----
 src/backend/access/transam/xloginsert.c |   5 +-
 src/backend/utils/misc/guc.c            |  23 +++-
 src/common/pg_lzcompress.c              | 134 +++++++++++++++++++++++-
 src/include/common/pg_lzcompress.h      |  49 +++++++--
 src/include/pg_config.h.in              |   3 +
 src/include/postgres.h                  |   3 +-
 src/test/Makefile                       |   8 +-
 src/test/toast/.gitignore               |   2 +
 src/test/toast/Makefile                 |  25 +++++
 src/test/toast/README                   |  25 +++++
 src/test/toast/t/001_lz4.pl             | 124 ++++++++++++++++++++++
 17 files changed, 619 insertions(+), 47 deletions(-)
 create mode 100644 src/test/toast/.gitignore
 create mode 100644 src/test/toast/Makefile
 create mode 100644 src/test/toast/README
 create mode 100644 src/test/toast/t/001_lz4.pl

diff --git a/configure b/configure
index 7a6bfc2339..edd2bfefd6 100755
--- a/configure
+++ b/configure
@@ -704,6 +704,7 @@ with_system_tzdata
 with_libxslt
 with_libxml
 XML2_CONFIG
+with_lz4
 UUID_EXTRA_OBJS
 with_uuid
 with_systemd
@@ -795,6 +796,7 @@ infodir
 docdir
 oldincludedir
 includedir
+runstatedir
 localstatedir
 sharedstatedir
 sysconfdir
@@ -859,6 +861,7 @@ with_readline
 with_libedit_preferred
 with_uuid
 with_ossp_uuid
+with_lz4
 with_libxml
 with_libxslt
 with_system_tzdata
@@ -932,6 +935,7 @@ datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
 docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1184,6 +1188,15 @@ do
   | -silent | --silent | --silen | --sile | --sil)
     silent=yes ;;
 
+  -runstatedir | --runstatedir | --runstatedi | --runstated \
+  | --runstate | --runstat | --runsta | --runst | --runs \
+  | --run | --ru | --r)
+    ac_prev=runstatedir ;;
+  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+  | --run=* | --ru=* | --r=*)
+    runstatedir=$ac_optarg ;;
+
   -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
     ac_prev=sbindir ;;
   -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1321,7 +1334,7 @@ fi
 for ac_var in	exec_prefix prefix bindir sbindir libexecdir datarootdir \
 		datadir sysconfdir sharedstatedir localstatedir includedir \
 		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
-		libdir localedir mandir
+		libdir localedir mandir runstatedir
 do
   eval ac_val=\$$ac_var
   # Remove trailing slashes.
@@ -1474,6 +1487,7 @@ Fine tuning of the installation directories:
   --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --oldincludedir=DIR     C header files for non-gcc [/usr/include]
@@ -1564,6 +1578,7 @@ Optional Packages:
                           prefer BSD Libedit over GNU Readline
   --with-uuid=LIB         build contrib/uuid-ossp using LIB (bsd,e2fs,ossp)
   --with-ossp-uuid        obsolete spelling of --with-uuid=ossp
+  --without-lz4           do not build with LZ4 support
   --with-libxml           build with XML support
   --with-libxslt          use XSLT support when building contrib/xml2
   --with-system-tzdata=DIR
@@ -8115,6 +8130,34 @@ fi
 
 
 
+#
+# LZ4
+#
+
+
+
+# Check whether --with-lz4 was given.
+if test "${with_lz4+set}" = set; then :
+  withval=$with_lz4;
+  case $withval in
+    yes)
+      :
+      ;;
+    no)
+      :
+      ;;
+    *)
+      as_fn_error $? "no argument expected for --with-lz4 option" "$LINENO" 5
+      ;;
+  esac
+
+else
+  with_lz4=yes
+
+fi
+
+
+
 
 #
 # XML
@@ -12661,6 +12704,55 @@ fi
 fi
 
 
+# for lz4 compression support
+if test "$with_lz4" = yes ; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for LZ4_sizeofState in -llz4" >&5
+$as_echo_n "checking for LZ4_sizeofState in -llz4... " >&6; }
+if ${ac_cv_lib_lz4_LZ4_sizeofState+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-llz4  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char LZ4_sizeofState ();
+int
+main ()
+{
+return LZ4_sizeofState ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_lz4_LZ4_sizeofState=yes
+else
+  ac_cv_lib_lz4_LZ4_sizeofState=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lz4_LZ4_sizeofState" >&5
+$as_echo "$ac_cv_lib_lz4_LZ4_sizeofState" >&6; }
+if test "x$ac_cv_lib_lz4_LZ4_sizeofState" = xyes; then :
+  COMPRESSION_LIBS=" -llz4"
+else
+  as_fn_error $? "library 'lz4' is required for LZ4 compression support" "$LINENO" 5
+fi
+
+
+$as_echo "#define HAVE_LZ4 1" >>confdefs.h
+
+fi
+LIBS="$LIBS$COMPRESSION_LIBS"
 
 ##
 ## Header files
@@ -13340,6 +13432,18 @@ fi
 
 done
 
+fi
+
+# for lz4 compression support
+if test "$with_lz4" = yes ; then
+  ac_fn_c_check_header_mongrel "$LINENO" "lz4.h" "ac_cv_header_lz4_h" "$ac_includes_default"
+if test "x$ac_cv_header_lz4_h" = xyes; then :
+
+else
+  as_fn_error $? "header file <lz4.h> is required for LZ4 support" "$LINENO" 5
+fi
+
+
 fi
 
 if test "$PORTNAME" = "win32" ; then
@@ -14683,7 +14787,7 @@ else
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -14729,7 +14833,7 @@ else
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -14753,7 +14857,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -14798,7 +14902,7 @@ else
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -14822,7 +14926,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
diff --git a/configure.in b/configure.in
index dde3eec89f..393a820cc2 100644
--- a/configure.in
+++ b/configure.in
@@ -915,6 +915,12 @@ fi
 AC_SUBST(with_uuid)
 AC_SUBST(UUID_EXTRA_OBJS)
 
+#
+# LZ4
+#
+PGAC_ARG_BOOL(with, lz4, yes,
+              [do not build with LZ4 support])
+AC_SUBST(with_lz4)
 
 #
 # XML
@@ -1263,6 +1269,14 @@ elif test "$with_uuid" = ossp ; then
 fi
 AC_SUBST(UUID_LIBS)
 
+# for lz4 compression support
+if test "$with_lz4" = yes ; then
+  AC_CHECK_LIB(lz4, LZ4_sizeofState,
+    [COMPRESSION_LIBS=" -llz4"],
+    [AC_MSG_ERROR([library 'lz4' is required for LZ4 compression support])])
+  AC_DEFINE([HAVE_LZ4], 1, [Define to 1 to build with LZ4 support])
+fi
+LIBS="$LIBS$COMPRESSION_LIBS"
 
 ##
 ## Header files
@@ -1443,6 +1457,11 @@ elif test "$with_uuid" = ossp ; then
       [AC_MSG_ERROR([header file <ossp/uuid.h> or <uuid.h> is required for OSSP UUID])])])
 fi
 
+# for lz4 compression support
+if test "$with_lz4" = yes ; then
+  AC_CHECK_HEADER(lz4.h, [], [AC_MSG_ERROR([header file <lz4.h> is required for LZ4 support])])
+fi
+
 if test "$PORTNAME" = "win32" ; then
    AC_CHECK_HEADERS(crtdefs.h)
 fi
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index c91e3e1550..81bd26b653 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1793,6 +1793,42 @@ include_dir 'conf.d'
      <title>Disk</title>
 
      <variablelist>
+
+     <varlistentry id="guc-compression-algorithm" xreflabel="compression_algorithm">
+      <term><varname>compression_algorithm</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>compression_algorithm</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Which compression algorithm to use for compressing
+        <acronym>TOAST</acronym> data and when
+        <xref linkend="guc-wal-compression"/> is turned on also for
+        <acronym>WAL</acronym>.
+        Possible values are:
+       </para>
+       <itemizedlist>
+        <listitem>
+        <para>
+         <literal>pglz</literal> (the internal PostgreSQL LZ family compression algorithm)
+        </para>
+        </listitem>
+        <listitem>
+        <para>
+         <literal>lz4</literal> (the LZ4 compression algorithm)
+        </para>
+        </listitem>
+       </itemizedlist>
+       <para>
+        Not all of these choices are available on all platforms.
+        The default is the pglz algorithm which is the one used by PostgrSQL
+        version 12 and earlier.
+        Only superusers can change this setting.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-temp-file-limit" xreflabel="temp_file_limit">
       <term><varname>temp_file_limit</varname> (<type>integer</type>)
       <indexterm>
@@ -2728,6 +2764,8 @@ include_dir 'conf.d'
         <xref linkend="guc-full-page-writes"/> is on or during a base backup.
         A compressed page image will be decompressed during WAL replay.
         The default value is <literal>off</literal>.
+        The compression used is the one specified by
+        <xref linkend="guc-compression-algorithm"/>
         Only superusers can change this setting.
        </para>
 
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 1047c77a63..306a0bb3d3 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -394,9 +394,8 @@ Further details appear in <xref linkend="storage-toast-inmemory"/>.
 
 <para>
 The compression technique used for either in-line or out-of-line compressed
-data is a fairly simple and very fast member
-of the LZ family of compression techniques.  See
-<filename>src/common/pg_lzcompress.c</filename> for the details.
+data is chosen based on the <xref linkend="guc-compression-algorithm"/>
+setting.
 </para>
 
 <sect2 id="storage-toast-ondisk">
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index dc3f207e1c..8c3d38db1c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -195,6 +195,7 @@ with_libxslt	= @with_libxslt@
 with_llvm	= @with_llvm@
 with_system_tzdata = @with_system_tzdata@
 with_uuid	= @with_uuid@
+with_lz4	= @with_lz4@
 with_zlib	= @with_zlib@
 enable_rpath	= @enable_rpath@
 enable_nls	= @enable_nls@
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 74233bb931..14b64936ab 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -52,7 +52,20 @@
 typedef struct toast_compress_header
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
-	int32		rawsize;
+	/*
+	 * The length cannot be more than 1GB due to general toast limitations
+	 * we have the 2 high bits to encode aditional information.
+	 *
+	 * We use the last (highest) bit to mark this toast as the "new
+	 * compression format" as the new pg_compress has it's own header
+	 * which and the original pglz format is not distiguishable in any
+	 * way from the format used by pg_compress. Thanks to this information
+	 * the toast_decompress_datum can pick to either directly use
+	 * pglz_decompress directly when dealing with data writen by older
+	 * versions of postgres or let pg_decompress to autodetect format.
+	 */
+	int32		rawsize:31;
+	uint32		cformat:1;
 } toast_compress_header;
 
 /*
@@ -61,10 +74,13 @@ typedef struct toast_compress_header
  */
 #define TOAST_COMPRESS_HDRSZ		((int32) sizeof(toast_compress_header))
 #define TOAST_COMPRESS_RAWSIZE(ptr) (((toast_compress_header *) (ptr))->rawsize)
+#define TOAST_COMPRESS_CFORMAT(ptr) (((toast_compress_header *) (ptr))->cformat)
 #define TOAST_COMPRESS_RAWDATA(ptr) \
 	(((char *) (ptr)) + TOAST_COMPRESS_HDRSZ)
 #define TOAST_COMPRESS_SET_RAWSIZE(ptr, len) \
 	(((toast_compress_header *) (ptr))->rawsize = (len))
+#define TOAST_COMPRESS_SET_CFORMAT(ptr, fmt) \
+	(((toast_compress_header *) (ptr))->cformat = (fmt))
 
 static void toast_delete_datum(Relation rel, Datum value, bool is_speculative);
 static Datum toast_save_datum(Relation rel, Datum value,
@@ -385,7 +401,7 @@ toast_raw_datum_size(Datum value)
 	else if (VARATT_IS_COMPRESSED(attr))
 	{
 		/* here, va_rawsize is just the payload size */
-		result = VARRAWSIZE_4B_C(attr) + VARHDRSZ;
+		result = TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ;
 	}
 	else if (VARATT_IS_SHORT(attr))
 	{
@@ -1363,6 +1379,7 @@ toast_compress_datum(Datum value)
 {
 	struct varlena *tmp;
 	int32		valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+	int32		buffer_capacity;
 	int32		len;
 
 	Assert(!VARATT_IS_EXTERNAL(DatumGetPointer(value)));
@@ -1376,11 +1393,11 @@ toast_compress_datum(Datum value)
 		valsize > PGLZ_strategy_default->max_input_size)
 		return PointerGetDatum(NULL);
 
-	tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(valsize) +
-									TOAST_COMPRESS_HDRSZ);
+	buffer_capacity = pg_compress_bound(valsize);
+	tmp = (struct varlena *) palloc(buffer_capacity + TOAST_COMPRESS_HDRSZ);
 
 	/*
-	 * We recheck the actual size even if pglz_compress() reports success,
+	 * We recheck the actual size even if pg_compress() reports success,
 	 * because it might be satisfied with having saved as little as one byte
 	 * in the compressed data --- which could turn into a net loss once you
 	 * consider header and alignment padding.  Worst case, the compressed
@@ -1389,14 +1406,17 @@ toast_compress_datum(Datum value)
 	 * only one header byte and no padding if the value is short enough.  So
 	 * we insist on a savings of more than 2 bytes to ensure we have a gain.
 	 */
-	len = pglz_compress(VARDATA_ANY(DatumGetPointer(value)),
-						valsize,
-						TOAST_COMPRESS_RAWDATA(tmp),
-						PGLZ_strategy_default);
+	len = pg_compress(VARDATA_ANY(DatumGetPointer(value)),
+					  valsize,
+					  TOAST_COMPRESS_RAWDATA(tmp),
+					  buffer_capacity,
+					  PGLZ_strategy_default);
+
 	if (len >= 0 &&
 		len + TOAST_COMPRESS_HDRSZ < valsize - 2)
 	{
 		TOAST_COMPRESS_SET_RAWSIZE(tmp, valsize);
+		TOAST_COMPRESS_SET_CFORMAT(tmp, 1);
 		SET_VARSIZE_COMPRESSED(tmp, len + TOAST_COMPRESS_HDRSZ);
 		/* successful compression */
 		return PointerGetDatum(tmp);
@@ -1520,7 +1540,7 @@ toast_save_datum(Relation rel, Datum value,
 		data_p = VARDATA(dval);
 		data_todo = VARSIZE(dval) - VARHDRSZ;
 		/* rawsize in a compressed datum is just the size of the payload */
-		toast_pointer.va_rawsize = VARRAWSIZE_4B_C(dval) + VARHDRSZ;
+		toast_pointer.va_rawsize = TOAST_COMPRESS_RAWSIZE(dval) + VARHDRSZ;
 		toast_pointer.va_extsize = data_todo;
 		/* Assert that the numbers look like it's compressed */
 		Assert(VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer));
@@ -2277,18 +2297,40 @@ static struct varlena *
 toast_decompress_datum(struct varlena *attr)
 {
 	struct varlena *result;
+	uint32			compression_format;
+	int32			raw_size;
 
 	Assert(VARATT_IS_COMPRESSED(attr));
 
-	result = (struct varlena *)
-		palloc(TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ);
-	SET_VARSIZE(result, TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ);
+	raw_size = TOAST_COMPRESS_RAWSIZE(attr);
 
-	if (pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
-						VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
-						VARDATA(result),
-						TOAST_COMPRESS_RAWSIZE(attr), true) < 0)
-		elog(ERROR, "compressed data is corrupted");
+	compression_format = TOAST_COMPRESS_CFORMAT(attr);
+
+	result = (struct varlena *) palloc(raw_size + VARHDRSZ);
+	SET_VARSIZE(result, raw_size + VARHDRSZ);
+
+	/*
+	 * Support for legacy compressed TOAST format which is always using pglz.
+	 */
+	switch (compression_format)
+	{
+		case 0:
+			if (pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
+								VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
+								VARDATA(result),
+								raw_size, true) < 0)
+				elog(ERROR, "compressed data is corrupted");
+			break;
+		case 1:
+			if (pg_decompress(TOAST_COMPRESS_RAWDATA(attr),
+							  VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
+							  VARDATA(result),
+							  raw_size, true) < 0)
+				elog(ERROR, "compressed data is corrupted");
+			break;
+		default:
+			pg_unreachable();
+	}
 
 	return result;
 }
@@ -2311,10 +2353,10 @@ toast_decompress_datum_slice(struct varlena *attr, int32 slicelength)
 
 	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
 
-	rawsize = pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
-							  VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
-							  VARDATA(result),
-							  slicelength, false);
+	rawsize = pg_decompress(TOAST_COMPRESS_RAWDATA(attr),
+							VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
+							VARDATA(result),
+							slicelength, false);
 	if (rawsize < 0)
 		elog(ERROR, "compressed data is corrupted");
 
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 3ec67d468b..76decbf426 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -830,11 +830,12 @@ XLogCompressBackupBlock(char *page, uint16 hole_offset, uint16 hole_length,
 		source = page;
 
 	/*
-	 * We recheck the actual size even if pglz_compress() reports success and
+	 * We recheck the actual size even if pg_compress() reports success and
 	 * see if the number of bytes saved by compression is larger than the
 	 * length of extra data needed for the compressed version of block image.
 	 */
-	len = pglz_compress(source, orig_len, dest, PGLZ_strategy_default);
+	len = pg_compress(source, orig_len, dest, PGLZ_MAX_BLCKSZ,
+					  PGLZ_strategy_default);
 	if (len >= 0 &&
 		len + extra_bytes < orig_len)
 	{
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index fc463601ff..04d99b97bf 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -42,6 +42,7 @@
 #include "commands/vacuum.h"
 #include "commands/variable.h"
 #include "commands/trigger.h"
+#include "common/pg_lzcompress.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "jit/jit.h"
@@ -470,6 +471,14 @@ static struct config_enum_entry shared_memory_options[] = {
 	{NULL, 0, false}
 };
 
+static const struct config_enum_entry compression_algorithm_options[] = {
+	{"pglz", COMPRESS_ALGO_PGLZ, false},
+#ifdef HAVE_LZ4
+	{"lz4", COMPRESS_ALGO_LZ4, false},
+#endif
+	{NULL, 0, false}
+};
+
 /*
  * Options for enum values stored in other modules
  */
@@ -4540,7 +4549,7 @@ static struct config_enum ConfigureNamesEnum[] =
 
 	{
 		{"ssl_max_protocol_version", PGC_SIGHUP, CONN_AUTH_SSL,
-			gettext_noop("Sets the maximum SSL/TLS protocol version to use."),
+			gettext_noop("Chooses the compression algorithm for TOAST and WAL."),
 			NULL,
 			GUC_SUPERUSER_ONLY
 		},
@@ -4550,6 +4559,18 @@ static struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"compression_algorithm", PGC_SIGHUP, RESOURCES_DISK,
+			gettext_noop("Sets the maximum SSL/TLS protocol version to use."),
+			NULL,
+			GUC_SUPERUSER_ONLY
+		},
+		&compression_algorithm,
+		COMPRESS_ALGO_PGLZ,
+		compression_algorithm_options,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/common/pg_lzcompress.c b/src/common/pg_lzcompress.c
index 988b3987d0..8e8b966d67 100644
--- a/src/common/pg_lzcompress.c
+++ b/src/common/pg_lzcompress.c
@@ -187,6 +187,7 @@
 
 #include "common/pg_lzcompress.h"
 
+int compression_algorithm = COMPRESS_ALGO_PGLZ;
 
 /* ----------
  * Local definitions
@@ -505,8 +506,8 @@ pglz_find_match(int16 *hstart, const char *input, const char *end,
  *		bytes written in buffer dest, or -1 if compression fails.
  * ----------
  */
-int32
-pglz_compress(const char *source, int32 slen, char *dest,
+static int32
+pglz_compress(const char *source, int32 slen, char *dest, int32 capacity,
 			  const PGLZ_Strategy *strategy)
 {
 	unsigned char *bp = (unsigned char *) dest;
@@ -771,3 +772,132 @@ pglz_decompress(const char *source, int32 slen, char *dest,
 	 */
 	return (char *) dp - dest;
 }
+
+#ifdef HAVE_LZ4
+#include "utils/elog.h"
+
+static int32
+lz4_compress(const char *source, int32 slen, char *dest, int32 capacity,
+			 const PGLZ_Strategy *strategy)
+{
+	int	ret;
+
+	ret = LZ4_compress_default(source, dest, slen, capacity);
+
+	/*
+	 * In case of compression error, return -1 which callers should take
+	 * as incompressible data.
+	 */
+	if (ret	== 0)
+		return -1;
+
+	return ret;
+}
+
+int32
+lz4_decompress(const char *source, int32 slen, char *dest,
+			   int32 rawsize, bool check_complete)
+{
+	int	ret;
+
+	if (check_complete)
+	{
+		ret = LZ4_decompress_safe(source, dest, slen, rawsize);
+
+		/*
+		 * Check we decompressed the right amount.
+		 */
+		if (ret != rawsize)
+			return -1;
+	}
+	else
+		ret = LZ4_decompress_safe_partial(source, dest, slen, rawsize,
+										  rawsize);
+
+	return ret;
+}
+#endif
+
+/*
+ * Compress using configured algorithm
+ */
+int32
+pg_compress(const char *source, int32 slen, char *dest, int32 capacity,
+			const PGLZ_Strategy *strategy)
+{
+	int32	ret;
+
+	switch (compression_algorithm)
+	{
+		case COMPRESS_ALGO_PGLZ:
+			dest[0] = COMPRESS_ALGO_PGLZ;
+			ret = pglz_compress(source, slen, &dest[1], capacity - 1,
+								strategy);
+			break;
+#ifdef HAVE_LZ4
+		case COMPRESS_ALGO_LZ4:
+			dest[0] = COMPRESS_ALGO_LZ4;
+			ret = lz4_compress(source, slen, &dest[1], capacity - 1,
+							   strategy);
+			break;
+#endif
+		default:
+			pg_unreachable();
+	}
+
+	if (ret >= 0)
+		return ret + 1;
+
+	return ret;
+}
+
+/*
+ * Decompress data compressed with one of the supported algorithms.
+ */
+int32
+pg_decompress(const char *source, int32 slen, char *dest,
+			  int32 rawsize, bool check_complete)
+{
+	switch (source[0])
+	{
+		case COMPRESS_ALGO_PGLZ:
+			return pglz_decompress(&source[1], slen - 1, dest, rawsize,
+								   check_complete);
+#ifdef HAVE_LZ4
+		case COMPRESS_ALGO_LZ4:
+			return lz4_decompress(&source[1], slen - 1, dest, rawsize,
+								  check_complete);
+#endif
+		default:
+			Assert(false); /* XXX: Can't elog here. */
+	}
+
+	pg_unreachable();
+}
+
+/*
+ * Compute the buffer size required by pg_compress for a configured algorithm
+ * including our header size.
+ */
+int32
+pg_compress_bound(int32 slen)
+{
+	switch (compression_algorithm)
+	{
+		case COMPRESS_ALGO_PGLZ:
+			/*
+			 * For pglz we allow 4 bytes for overrun before detecting
+			 * compression failure.
+			 */
+			return slen + 4 + SIZEOF_PG_COMPRESS_HEADER;
+#ifdef HAVE_LZ4
+		case COMPRESS_ALGO_LZ4:
+			/* LZ4 provides direct interface for calculating needed space. */
+			return LZ4_compressBound(slen) + SIZEOF_PG_COMPRESS_HEADER;
+#endif
+		default:
+			pg_unreachable();
+	}
+
+	pg_unreachable();
+}
diff --git a/src/include/common/pg_lzcompress.h b/src/include/common/pg_lzcompress.h
index 555576436c..04453f4574 100644
--- a/src/include/common/pg_lzcompress.h
+++ b/src/include/common/pg_lzcompress.h
@@ -10,15 +10,35 @@
 #ifndef _PG_LZCOMPRESS_H_
 #define _PG_LZCOMPRESS_H_
 
+#ifdef HAVE_LZ4
+#include "lz4.h"
 
-/* ----------
- * PGLZ_MAX_OUTPUT -
+#define SIZEOF_PG_COMPRESS_HEADER	1
+/*
+ * Macro version of pg_compress_bound, less precise, usable in places where
+ * we need compile time size information.
+ * We add +1 compared to what algorithms need because that's the size of
+ * pg_compress header.
+ */
+#define PGLZ_MAX_OUTPUT(_dlen)	(Max((_dlen) + 4, LZ4_COMPRESSBOUND(_dlen)) + \
+								 SIZEOF_PG_COMPRESS_HEADER)
+#else
+#define PGLZ_MAX_OUTPUT(_dlen)	((_dlen) + 4 + SIZEOF_PG_COMPRESS_HEADER)
+#endif
+
+/*
+ * PGLZCompressionAlgo
  *
- *		Macro to compute the buffer size required by pglz_compress().
- *		We allow 4 bytes for overrun before detecting compression failure.
- * ----------
+ * Which algorithm to use for TOAST and WAL compression.
+ *
+ * COMPRESS_ALGO_PGLZ - use the builtin pglz algorithm
+ * COMPRESS_ALGO_LZ4 - use the LZ4 library
  */
-#define PGLZ_MAX_OUTPUT(_dlen)			((_dlen) + 4)
+typedef enum
+{
+	COMPRESS_ALGO_PGLZ = 0,
+	COMPRESS_ALGO_LZ4
+}	PGLZCompressAlgo;
 
 
 /* ----------
@@ -78,14 +98,25 @@ typedef struct PGLZ_Strategy
 extern const PGLZ_Strategy *const PGLZ_strategy_default;
 extern const PGLZ_Strategy *const PGLZ_strategy_always;
 
+/*
+ * Compression algorithm.
+ */
+
+extern int	compression_algorithm;
 
 /* ----------
  * Global function declarations
  * ----------
  */
-extern int32 pglz_compress(const char *source, int32 slen, char *dest,
-						   const PGLZ_Strategy *strategy);
 extern int32 pglz_decompress(const char *source, int32 slen, char *dest,
-							 int32 rawsize, bool check_complete);
+				int32 rawsize, bool check_complete);
+extern int32 lz4_decompress(const char *source, int32 slen, char *dest,
+				int32 rawsize, bool check_complete);
+extern int32 pg_compress(const char *source, int32 slen, char *dest, int32 capacity,
+			const PGLZ_Strategy *strategy);
+extern int32 pg_decompress(const char *source, int32 slen, char *dest,
+			  int32 rawsize, bool check_complete);
+
+extern int32 pg_compress_bound(int32 slen);
 
 #endif							/* _PG_LZCOMPRESS_H_ */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 512213aa32..5e37aab4c5 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -718,6 +718,9 @@
 /* Define to 1 if you have the <uuid/uuid.h> header file. */
 #undef HAVE_UUID_UUID_H
 
+/* Define to 1 to build with LZ4 support. */
+#undef HAVE_LZ4
+
 /* Define to 1 if you have the <wchar.h> header file. */
 #undef HAVE_WCHAR_H
 
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 057a3413ac..9578588880 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -145,7 +145,8 @@ typedef union
 	struct						/* Compressed-in-line format */
 	{
 		uint32		va_header;
-		uint32		va_rawsize; /* Original data size (excludes header) */
+		uint32		va_rawsize:31; /* Original data size (excludes header) */
+		int			va_cformat:1;
 		char		va_data[FLEXIBLE_ARRAY_MEMBER]; /* Compressed data */
 	}			va_compressed;
 } varattrib_4b;
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..c6287e7a04 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -32,12 +32,18 @@ ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
 SUBDIRS += ssl
 endif
 endif
+ifeq ($(with_lz4),yes)
+ifneq (,$(filter toast,$(PG_TEST_EXTRA)))
+SUBDIRS += toast
+endif
+endif
 
 # We don't build or execute these by default, but we do want "make
 # clean" etc to recurse into them.  (We must filter out those that we
 # have conditionally included into SUBDIRS above, else there will be
 # make confusion.)
-ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap locale thread ssl)
+ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap locale thread \
+				 ssl toast)
 
 # We want to recurse to all subdirs for all standard targets, except that
 # installcheck and install should not recurse into the subdirectory "modules".
diff --git a/src/test/toast/.gitignore b/src/test/toast/.gitignore
new file mode 100644
index 0000000000..871e943d50
--- /dev/null
+++ b/src/test/toast/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/toast/Makefile b/src/test/toast/Makefile
new file mode 100644
index 0000000000..5a1ff09a13
--- /dev/null
+++ b/src/test/toast/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/toast
+#
+# Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/toast/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/recovery
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export with_lz4
+
+check:
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
+
+clean distclean maintainer-clean:
+	rm -rf tmp_check
diff --git a/src/test/toast/README b/src/test/toast/README
new file mode 100644
index 0000000000..8802ecbe06
--- /dev/null
+++ b/src/test/toast/README
@@ -0,0 +1,25 @@
+src/test/tosat/README
+
+Regression tests for TOAST compression
+======================================
+
+This directory contains a test suite for TOAST compression and replication.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+Also, to use "make installcheck", you must have built and installed
+contrib/test_decoding in addition to the core code.
+
+Run
+    make check
+or
+    make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested.  With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops several test Postgres
+clusters.
diff --git a/src/test/toast/t/001_lz4.pl b/src/test/toast/t/001_lz4.pl
new file mode 100644
index 0000000000..89f7cd177f
--- /dev/null
+++ b/src/test/toast/t/001_lz4.pl
@@ -0,0 +1,124 @@
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+use File::Copy;
+
+use FindBin;
+use lib $FindBin::RealBin;
+
+if ($ENV{with_lz4} eq 'yes')
+{
+	plan tests => 10;
+}
+else
+{
+	plan skip_all => 'LZ4 not supported by this build';
+}
+
+#### Set up the server.
+note "setting up data directory";
+my $node = get_new_node('master');
+$node->init;
+$node->append_conf('postgresql.conf', qq[
+compression_algorithm = lz4
+wal_compression = on
+]);
+$node->start;
+
+# Run this before we lock down access below.
+my $result = $node->safe_psql('postgres', "SHOW compression_algorithm");
+is($result, 'lz4', 'compression_algorithm set to lz4');
+
+$node->safe_psql('postgres',
+	qq[
+	CREATE TABLE toast_test (
+		id int,
+		data text
+	)]);
+
+$node->safe_psql('postgres',
+	'ALTER TABLE toast_test ALTER COLUMN data SET STORAGE MAIN');
+
+# This will actually be compressed inline as it's easy to compress
+$node->safe_psql('postgres',
+qq[
+	INSERT INTO toast_test
+		SELECT n, repeat('toasted', 1000)
+		  FROM generate_series(1, 100) s(n);
+]);
+
+
+my $toast_size = $node->safe_psql('postgres',
+qq[
+	SELECT pg_relation_size((SELECT reltoastrelid FROM pg_catalog.pg_class WHERE relname = 'toast_test'));
+]);
+
+ok($toast_size == 0, 'toast table is used');
+
+$node->safe_psql('postgres',
+	'ALTER TABLE toast_test ALTER COLUMN data SET STORAGE EXTENDED');
+
+# Something less easily compressable so that it's in TOAST table
+$node->safe_psql('postgres',
+qq[
+	INSERT INTO toast_test
+		SELECT n, (SELECT string_agg(md5(t::text),'')
+		             FROM generate_series(1, 200) q(t))
+		  FROM generate_series(101, 200) s(n);
+]);
+
+$toast_size = $node->safe_psql('postgres',
+qq[
+	SELECT pg_relation_size((SELECT reltoastrelid FROM pg_catalog.pg_class WHERE relname = 'toast_test'));
+]);
+
+ok($toast_size > 0, 'toast table is used');
+
+# check if we can select data
+is($node->safe_psql('postgres',
+		qq[SELECT id, length(data) FROM toast_test WHERE id = 1]),
+	'1|7000', 'can select compressed data');
+is($node->safe_psql('postgres',
+		qq[SELECT id, length(data) FROM toast_test WHERE id = 200]),
+	'200|6400', 'can select TOAST compressed data');
+
+# test slicing
+is($node->safe_psql('postgres',
+		qq[SELECT id, substr(data, 1, 10) FROM toast_test WHERE id = 50]),
+	'50|toastedtoa', 'slicing of compressed data works');
+
+is($node->safe_psql('postgres',
+		qq[SELECT id, substr(data, 1, 10) FROM toast_test WHERE id = 150]),
+	'150|c4ca4238a0', 'slicing of TOAST works');
+
+$node->append_conf('postgresql.conf', qq[
+compression_algorithm = pglz
+]);
+$node->reload;
+
+# Run this before we lock down access below.
+$result = $node->safe_psql('postgres', "SHOW compression_algorithm");
+is($result, 'pglz', 'compression_algorithm set to pglz');
+
+$node->safe_psql('postgres',
+qq[
+	INSERT INTO toast_test
+		SELECT n, (SELECT string_agg(md5(t::text),'')
+		             FROM generate_series(1, 200) q(t))
+		  FROM generate_series(201, 300) s(n);
+]);
+
+is($node->safe_psql('postgres',
+		qq[SELECT id, length(data) FROM toast_test WHERE id IN (200, 201)]),
+q[200|6400
+201|6400], 'can select TOAST with different compression for different rows');
+
+is($node->safe_psql('postgres',
+		qq[SELECT id, substr(data, 1, 10) FROM toast_test WHERE id IN (150, 250)]),
+q[150|c4ca4238a0
+250|c4ca4238a0], 'slicing of TOAST works with different compression for different row');
+
+done_testing();
-- 
2.20.1


--------------E1FE46135503D8F9FB86E4E2--





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* [PATCH 3/3] Extended statistics on expressions
@ 2020-12-03 15:19 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Tomas Vondra @ 2020-12-03 15:19 UTC (permalink / raw)

Allow defining extended statistics on expressions, not just simple
column references. With this commit, it's possible to do things like

  CREATE TABLE t (a int);
  CREATE STATISTICS s ON mod(a,10), mod(a,20) FROM t;

and the collected statistics will be useful for estimating queries
using those expressions in various places, like

  SELECT * FROM t WHERE mod(a,10) = 0 AND mod(a,20) = 0;

or

  SELECT mod(a,10), mod(a,20) FROM t GROUP BY 1, 2;

The commit also adds a new statistics type "expressions" which builds
the usual per-column statistics for each expression, allowing better
estimates even for queries with just a single expression, which are
not affected by multi-column statistics. This achieves the same goal
as creating expression indexes, without index maintenance overhead.
---
 doc/src/sgml/catalogs.sgml                    |  236 +++
 doc/src/sgml/ref/create_statistics.sgml       |  108 +-
 src/backend/catalog/Makefile                  |    8 +-
 src/backend/catalog/system_views.sql          |   74 +
 src/backend/commands/statscmds.c              |  322 +++-
 src/backend/nodes/copyfuncs.c                 |   14 +
 src/backend/nodes/equalfuncs.c                |   13 +
 src/backend/nodes/outfuncs.c                  |   12 +
 src/backend/optimizer/util/plancat.c          |   62 +
 src/backend/parser/gram.y                     |   38 +-
 src/backend/parser/parse_agg.c                |   10 +
 src/backend/parser/parse_expr.c               |    6 +
 src/backend/parser/parse_func.c               |    3 +
 src/backend/parser/parse_utilcmd.c            |  121 +-
 src/backend/statistics/dependencies.c         |  366 +++-
 src/backend/statistics/extended_stats.c       | 1486 ++++++++++++++++-
 src/backend/statistics/mcv.c                  |  293 +++-
 src/backend/statistics/mvdistinct.c           |   99 +-
 src/backend/tcop/utility.c                    |   23 +-
 src/backend/utils/adt/ruleutils.c             |  287 +++-
 src/backend/utils/adt/selfuncs.c              |  407 ++++-
 src/bin/pg_dump/t/002_pg_dump.pl              |   12 +
 src/bin/psql/describe.c                       |   66 +-
 src/include/catalog/pg_proc.dat               |    8 +
 src/include/catalog/pg_statistic_ext.h        |    4 +
 src/include/catalog/pg_statistic_ext_data.h   |    1 +
 src/include/nodes/nodes.h                     |    1 +
 src/include/nodes/parsenodes.h                |   16 +
 src/include/nodes/pathnodes.h                 |    1 +
 src/include/parser/parse_node.h               |    1 +
 src/include/parser/parse_utilcmd.h            |    2 +
 .../statistics/extended_stats_internal.h      |   40 +-
 src/include/statistics/statistics.h           |    2 +
 .../regress/expected/create_table_like.out    |   20 +-
 src/test/regress/expected/rules.out           |   75 +
 src/test/regress/expected/stats_ext.out       |  684 +++++++-
 src/test/regress/sql/create_table_like.sql    |    2 +
 src/test/regress/sql/stats_ext.sql            |  314 +++-
 38 files changed, 4866 insertions(+), 371 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3a2266526c..48bcfb1d42 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9385,6 +9385,11 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>extended planner statistics</entry>
      </row>
 
+     <row>
+      <entry><link linkend="view-pg-stats-ext-exprs"><structname>pg_stats_ext_exprs</structname></link></entry>
+      <entry>extended planner statistics for expressions</entry>
+     </row>
+
      <row>
       <entry><link linkend="view-pg-tables"><structname>pg_tables</structname></link></entry>
       <entry>tables</entry>
@@ -12947,6 +12952,237 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
+ <sect1 id="view-pg-stats-ext-exprs">
+  <title><structname>pg_stats_ext_exprs</structname></title>
+
+  <indexterm zone="view-pg-stats-ext-exprs">
+   <primary>pg_stats_ext_exprs</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_stats_ext_exprs</structname> provides access to
+   the information stored in the <link
+   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>
+   and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   catalogs.  This view allows access only to rows of
+   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link> and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   that correspond to tables the user has permission to read, and therefore
+   it is safe to allow public read access to this view.
+  </para>
+
+  <para>
+   <structname>pg_stats_ext_exprs</structname> is also designed to present
+   the information in a more readable format than the underlying catalogs
+   &mdash; at the cost that its schema must be extended whenever the structure
+   of statistics <link linkend="catalog-pg-statistic"><structname>pg_statistic</structname></link> changes.
+  </para>
+
+  <table>
+   <title><structname>pg_stats_ext_exprs</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>schemaname</structfield> <type>name</type>
+       (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield>)
+      </para>
+      <para>
+       Name of schema containing table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>tablename</structfield> <type>name</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+      </para>
+      <para>
+       Name of table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>statistics_schemaname</structfield> <type>name</type>
+       (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield>)
+      </para>
+      <para>
+       Name of schema containing extended statistic
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>statistics_name</structfield> <type>name</type>
+       (references <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>.<structfield>stxname</structfield>)
+      </para>
+      <para>
+       Name of extended statistics
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>statistics_owner</structfield> <type>name</type>
+       (references <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>rolname</structfield>)
+      </para>
+      <para>
+       Owner of the extended statistics
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>expr</structfield> <type>text</type>
+      </para>
+      <para>
+       Expression the extended statistics is defined on
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>null_frac</structfield> <type>float4</type>
+      </para>
+      <para>
+       Fraction of column entries that are null
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>avg_width</structfield> <type>int4</type>
+      </para>
+      <para>
+       Average width in bytes of column's entries
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>n_distinct</structfield> <type>float4</type>
+      </para>
+      <para>
+       If greater than zero, the estimated number of distinct values in the
+       column.  If less than zero, the negative of the number of distinct
+       values divided by the number of rows.  (The negated form is used when
+       <command>ANALYZE</command> believes that the number of distinct values is
+       likely to increase as the table grows; the positive form is used when
+       the column seems to have a fixed number of possible values.)  For
+       example, -1 indicates a unique column in which the number of distinct
+       values is the same as the number of rows.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>most_common_vals</structfield> <type>anyarray</type>
+      </para>
+      <para>
+       A list of the most common values in the column. (Null if
+       no values seem to be more common than any others.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>most_common_freqs</structfield> <type>float4[]</type>
+      </para>
+      <para>
+       A list of the frequencies of the most common values,
+       i.e., number of occurrences of each divided by total number of rows.
+       (Null when <structfield>most_common_vals</structfield> is.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>histogram_bounds</structfield> <type>anyarray</type>
+      </para>
+      <para>
+       A list of values that divide the column's values into groups of
+       approximately equal population.  The values in
+       <structfield>most_common_vals</structfield>, if present, are omitted from this
+       histogram calculation.  (This column is null if the column data type
+       does not have a <literal>&lt;</literal> operator or if the
+       <structfield>most_common_vals</structfield> list accounts for the entire
+       population.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>correlation</structfield> <type>float4</type>
+      </para>
+      <para>
+       Statistical correlation between physical row ordering and
+       logical ordering of the column values.  This ranges from -1 to +1.
+       When the value is near -1 or +1, an index scan on the column will
+       be estimated to be cheaper than when it is near zero, due to reduction
+       of random access to the disk.  (This column is null if the column data
+       type does not have a <literal>&lt;</literal> operator.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>most_common_elems</structfield> <type>anyarray</type>
+      </para>
+      <para>
+       A list of non-null element values most often appearing within values of
+       the column. (Null for scalar types.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>most_common_elem_freqs</structfield> <type>float4[]</type>
+      </para>
+      <para>
+       A list of the frequencies of the most common element values, i.e., the
+       fraction of rows containing at least one instance of the given value.
+       Two or three additional values follow the per-element frequencies;
+       these are the minimum and maximum of the preceding per-element
+       frequencies, and optionally the frequency of null elements.
+       (Null when <structfield>most_common_elems</structfield> is.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>elem_count_histogram</structfield> <type>float4[]</type>
+      </para>
+      <para>
+       A histogram of the counts of distinct non-null element values within the
+       values of the column, followed by the average number of distinct
+       non-null elements.  (Null for scalar types.)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The maximum number of entries in the array fields can be controlled on a
+   column-by-column basis using the <link linkend="sql-altertable"><command>ALTER
+   TABLE SET STATISTICS</command></link>
+   command, or globally by setting the
+   <xref linkend="guc-default-statistics-target"/> run-time parameter.
+  </para>
+
+ </sect1>
+
  <sect1 id="view-pg-tables">
   <title><structname>pg_tables</structname></title>
 
diff --git a/doc/src/sgml/ref/create_statistics.sgml b/doc/src/sgml/ref/create_statistics.sgml
index 4363be50c3..783cea61a2 100644
--- a/doc/src/sgml/ref/create_statistics.sgml
+++ b/doc/src/sgml/ref/create_statistics.sgml
@@ -21,9 +21,13 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
+CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_name</replaceable>
+    ON ( <replaceable class="parameter">expression</replaceable> )
+    FROM <replaceable class="parameter">table_name</replaceable>
+
 CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_name</replaceable>
     [ ( <replaceable class="parameter">statistics_kind</replaceable> [, ... ] ) ]
-    ON <replaceable class="parameter">column_name</replaceable>, <replaceable class="parameter">column_name</replaceable> [, ...]
+    ON { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [, ...]
     FROM <replaceable class="parameter">table_name</replaceable>
 </synopsis>
 
@@ -39,6 +43,16 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na
    database and will be owned by the user issuing the command.
   </para>
 
+  <para>
+   The <command>CREATE STATISTICS</command> command has two basic forms. The
+   simple variant allows to build statistics for a single expression, does
+   not allow specifying any statistics kinds and provides benefits similar
+   to an expression index. The full variant allows defining statistics objects
+   on multiple columns and expressions, and pick which statistics kinds will
+   be built. The per-expression statistics are built automatically when there
+   is at least one expression.
+  </para>
+
   <para>
    If a schema name is given (for example, <literal>CREATE STATISTICS
    myschema.mystat ...</literal>) then the statistics object is created in the
@@ -86,7 +100,9 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na
       dependency statistics, and <literal>mcv</literal> which enables
       most-common values lists.
       If this clause is omitted, all supported statistics kinds are
-      included in the statistics object.
+      included in the statistics object. Expression statistics are included
+      automatically when the statistics definition includes complex
+      expressions and not just simple column references.
       For more information, see <xref linkend="planner-stats-extended"/>
       and <xref linkend="multivariate-statistics-examples"/>.
      </para>
@@ -104,6 +120,17 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">expression</replaceable></term>
+    <listitem>
+     <para>
+      The expression to be covered by the computed statistics. In this case
+      only a single expression is required, in which case only the expression
+      statistics kind is allowed. The order of expressions is insignificant.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
@@ -125,6 +152,22 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na
    reading it.  Once created, however, the ownership of the statistics
    object is independent of the underlying table(s).
   </para>
+
+  <para>
+   Creating expression statistics is allowed only when expressions are given.
+   Expression statistics are per-expression and are similar to creating an
+   index on the expression, except that they avoid the overhead of the index.
+  </para>
+
+  <para>
+   All functions and operators used in a statistics definition must be
+   <quote>immutable</quote>, that is, their results must depend only on
+   their arguments and never on any outside influence (such as
+   the contents of another table or the current time).  This restriction
+   ensures that the behavior of the statistics is well-defined.  To use a
+   user-defined function in a statistics expression, remember to mark
+   the function immutable when you create it.
+  </para>
  </refsect1>
 
  <refsect1 id="sql-createstatistics-examples">
@@ -196,6 +239,67 @@ EXPLAIN ANALYZE SELECT * FROM t2 WHERE (a = 1) AND (b = 2);
    in the table, allowing it to generate better estimates in both cases.
   </para>
 
+  <para>
+   Create table <structname>t3</structname> with a single timestamp column,
+   and run a query using an expression on that column.  Without the
+   extended statistics, the planner has no information about data
+   distribution for reasults of those expression, and uses default
+   estimates as illustrated by the first query.  The planner also does
+   not realize the value of the second column fully defines the value
+   of the other column, because date truncated to day still identifies
+   the month). Then expression and ndistinct statistics are built on
+   those two columns:
+
+<programlisting>
+CREATE TABLE t3 (
+    a   timestamp
+);
+
+INSERT INTO t3 SELECT i FROM generate_series('2020-01-01'::timestamp,
+                                             '2020-12-31'::timestamp,
+                                             '1 minute'::interval) s(i);
+
+ANALYZE t3;
+
+-- the number of matching rows will be drastically underestimated:
+EXPLAIN ANALYZE SELECT * FROM t3
+  WHERE date_trunc('month', a) = '2020-01-01'::timestamp;
+
+EXPLAIN ANALYZE SELECT * FROM t3
+  WHERE date_trunc('day', a) BETWEEN '2020-01-01'::timestamp
+                                 AND '2020-06-30'::timestamp;
+
+EXPLAIN ANALYZE SELECT date_trunc('month', a), date_trunc('day', a)
+   FROM t3 GROUP BY 1, 2;
+
+-- per-expression statistics are built automatically
+CREATE STATISTICS s3 (ndistinct) ON date_trunc('month', a), date_trunc('day', a) FROM t3;
+
+ANALYZE t1;
+
+-- now the row count estimates are more accurate:
+EXPLAIN ANALYZE SELECT * FROM t3
+  WHERE date_trunc('month', a) = '2020-01-01'::timestamp;
+
+EXPLAIN ANALYZE SELECT * FROM t3
+  WHERE date_trunc('day', a) BETWEEN '2020-01-01'::timestamp
+                                 AND '2020-06-30'::timestamp;
+
+EXPLAIN ANALYZE SELECT date_trunc('month', a), date_trunc('day', a)
+   FROM t3 GROUP BY 1, 2;
+</programlisting>
+
+   Without expression and ndistinct statistics, the planner would assume
+   that the two <literal>WHERE</literal> and <literal>GROUP BY</literal>
+   conditions are independent, and would multiply their selectivities
+   together to arrive at a much-too-small row count estimate in the first
+   two queries, and a much-too-high group count estimate in the aggregate
+   query. This is further exacerbated by the lack of accurate statistics
+   for the expressions, forcing the planner to use default selectivities.
+   With such statistics, the planner recognizes that the conditions are
+   correlated and arrives at much more accurate estimates.
+  </para>
+
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index c85f0ca7b6..fa91ff1c42 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -49,15 +49,15 @@ include $(top_srcdir)/src/backend/common.mk
 
 # Note: the order of this list determines the order in which the catalog
 # header files are assembled into postgres.bki.  BKI_BOOTSTRAP catalogs
-# must appear first, and there are reputedly other, undocumented ordering
-# dependencies.
+# must appear first, and pg_statistic before pg_statistic_ext_data, and
+# there are reputedly other, undocumented ordering dependencies.
 CATALOG_HEADERS := \
 	pg_proc.h pg_type.h pg_attribute.h pg_class.h \
 	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
 	pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
 	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
-	pg_statistic_ext.h pg_statistic_ext_data.h \
-	pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
+	pg_statistic.h pg_statistic_ext.h pg_statistic_ext_data.h \
+	pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
 	pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
 	pg_database.h pg_db_role_setting.h pg_tablespace.h \
 	pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5d89e77dbe..bd2a7c2ac2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -264,6 +264,7 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
                   JOIN pg_attribute a
                        ON (a.attrelid = s.stxrelid AND a.attnum = k)
            ) AS attnames,
+           pg_get_statisticsobjdef_expressions(s.oid) as exprs,
            s.stxkind AS kinds,
            sd.stxdndistinct AS n_distinct,
            sd.stxddependencies AS dependencies,
@@ -290,6 +291,79 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
                 WHERE NOT has_column_privilege(c.oid, a.attnum, 'select') )
     AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
 
+CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
+    SELECT cn.nspname AS schemaname,
+           c.relname AS tablename,
+           sn.nspname AS statistics_schemaname,
+           s.stxname AS statistics_name,
+           pg_get_userbyid(s.stxowner) AS statistics_owner,
+           stat.expr,
+           (stat.a).stanullfrac AS null_frac,
+           (stat.a).stawidth AS avg_width,
+           (stat.a).stadistinct AS n_distinct,
+           (CASE
+               WHEN (stat.a).stakind1 = 1 THEN (stat.a).stavalues1
+               WHEN (stat.a).stakind2 = 1 THEN (stat.a).stavalues2
+               WHEN (stat.a).stakind3 = 1 THEN (stat.a).stavalues3
+               WHEN (stat.a).stakind4 = 1 THEN (stat.a).stavalues4
+               WHEN (stat.a).stakind5 = 1 THEN (stat.a).stavalues5
+           END) AS most_common_vals,
+           (CASE
+               WHEN (stat.a).stakind1 = 1 THEN (stat.a).stanumbers1
+               WHEN (stat.a).stakind2 = 1 THEN (stat.a).stanumbers2
+               WHEN (stat.a).stakind3 = 1 THEN (stat.a).stanumbers3
+               WHEN (stat.a).stakind4 = 1 THEN (stat.a).stanumbers4
+               WHEN (stat.a).stakind5 = 1 THEN (stat.a).stanumbers5
+           END) AS most_common_freqs,
+           (CASE
+               WHEN (stat.a).stakind1 = 2 THEN (stat.a).stavalues1
+               WHEN (stat.a).stakind2 = 2 THEN (stat.a).stavalues2
+               WHEN (stat.a).stakind3 = 2 THEN (stat.a).stavalues3
+               WHEN (stat.a).stakind4 = 2 THEN (stat.a).stavalues4
+               WHEN (stat.a).stakind5 = 2 THEN (stat.a).stavalues5
+           END) AS histogram_bounds,
+           (CASE
+               WHEN (stat.a).stakind1 = 3 THEN (stat.a).stanumbers1[1]
+               WHEN (stat.a).stakind2 = 3 THEN (stat.a).stanumbers2[1]
+               WHEN (stat.a).stakind3 = 3 THEN (stat.a).stanumbers3[1]
+               WHEN (stat.a).stakind4 = 3 THEN (stat.a).stanumbers4[1]
+               WHEN (stat.a).stakind5 = 3 THEN (stat.a).stanumbers5[1]
+           END) correlation,
+           (CASE
+               WHEN (stat.a).stakind1 = 4 THEN (stat.a).stavalues1
+               WHEN (stat.a).stakind2 = 4 THEN (stat.a).stavalues2
+               WHEN (stat.a).stakind3 = 4 THEN (stat.a).stavalues3
+               WHEN (stat.a).stakind4 = 4 THEN (stat.a).stavalues4
+               WHEN (stat.a).stakind5 = 4 THEN (stat.a).stavalues5
+           END) AS most_common_elems,
+           (CASE
+               WHEN (stat.a).stakind1 = 4 THEN (stat.a).stanumbers1
+               WHEN (stat.a).stakind2 = 4 THEN (stat.a).stanumbers2
+               WHEN (stat.a).stakind3 = 4 THEN (stat.a).stanumbers3
+               WHEN (stat.a).stakind4 = 4 THEN (stat.a).stanumbers4
+               WHEN (stat.a).stakind5 = 4 THEN (stat.a).stanumbers5
+           END) AS most_common_elem_freqs,
+           (CASE
+               WHEN (stat.a).stakind1 = 5 THEN (stat.a).stanumbers1
+               WHEN (stat.a).stakind2 = 5 THEN (stat.a).stanumbers2
+               WHEN (stat.a).stakind3 = 5 THEN (stat.a).stanumbers3
+               WHEN (stat.a).stakind4 = 5 THEN (stat.a).stanumbers4
+               WHEN (stat.a).stakind5 = 5 THEN (stat.a).stanumbers5
+           END) AS elem_count_histogram
+    FROM pg_statistic_ext s JOIN pg_class c ON (c.oid = s.stxrelid)
+         JOIN pg_statistic_ext_data sd ON (s.oid = sd.stxoid)
+         LEFT JOIN pg_namespace cn ON (cn.oid = c.relnamespace)
+         LEFT JOIN pg_namespace sn ON (sn.oid = s.stxnamespace)
+         LEFT JOIN LATERAL (
+             SELECT
+                 *
+             FROM (
+                 SELECT
+                     unnest(pg_get_statisticsobjdef_expressions(s.oid)) AS expr,
+                     unnest(sd.stxdexpr)::pg_statistic AS a
+             ) x
+         ) stat ON sd.stxdexpr IS NOT NULL;
+
 -- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
 REVOKE ALL on pg_statistic_ext_data FROM public;
 
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 114ad77142..7c51224f71 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -29,6 +29,8 @@
 #include "commands/comment.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "statistics/statistics.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -62,7 +64,8 @@ ObjectAddress
 CreateStatistics(CreateStatsStmt *stmt)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
-	int			numcols = 0;
+	int			nattnums = 0;
+	int			numcols;
 	char	   *namestr;
 	NameData	stxname;
 	Oid			statoid;
@@ -74,21 +77,25 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Datum		datavalues[Natts_pg_statistic_ext_data];
 	bool		datanulls[Natts_pg_statistic_ext_data];
 	int2vector *stxkeys;
+	List	   *stxexprs = NIL;
+	Datum		exprsDatum;
 	Relation	statrel;
 	Relation	datarel;
 	Relation	rel = NULL;
 	Oid			relid;
 	ObjectAddress parentobject,
 				myself;
-	Datum		types[3];		/* one for each possible type of statistic */
+	Datum		types[4];		/* one for each possible type of statistic */
 	int			ntypes;
 	ArrayType  *stxkind;
 	bool		build_ndistinct;
 	bool		build_dependencies;
 	bool		build_mcv;
+	bool		build_expressions;
 	bool		requested_type = false;
 	int			i;
 	ListCell   *cell;
+	ListCell   *cell2;
 
 	Assert(IsA(stmt, CreateStatsStmt));
 
@@ -135,6 +142,13 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!pg_class_ownercheck(RelationGetRelid(rel), stxowner))
 			aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
 						   RelationGetRelationName(rel));
+
+		/* Creating statistics on system catalogs is not allowed */
+		if (!allowSystemTableMods && IsSystemRelation(rel))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied: \"%s\" is a system catalog",
+							RelationGetRelationName(rel))));
 	}
 
 	Assert(rel);
@@ -183,72 +197,169 @@ CreateStatistics(CreateStatsStmt *stmt)
 	}
 
 	/*
-	 * Currently, we only allow simple column references in the expression
-	 * list.  That will change someday, and again the grammar already supports
-	 * it so we have to enforce restrictions here.  For now, we can convert
-	 * the expression list to a simple array of attnums.  While at it, enforce
-	 * some constraints.
+	 * Make sure no more than STATS_MAX_DIMENSIONS columns are used. There
+	 * might be duplicates and so on, but we'll deal with those later.
+	 */
+	numcols = list_length(stmt->exprs);
+	if (numcols > STATS_MAX_DIMENSIONS)
+		ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot have more than %d columns in statistics",
+						STATS_MAX_DIMENSIONS)));
+
+	/*
+	 * Convert the expression list to a simple array of attnums, but also
+	 * keep a list of more complex expressions.  While at it, enforce some
+	 * constraints.
 	 */
 	foreach(cell, stmt->exprs)
 	{
 		Node	   *expr = (Node *) lfirst(cell);
-		ColumnRef  *cref;
-		char	   *attname;
+		StatsElem  *selem;
 		HeapTuple	atttuple;
 		Form_pg_attribute attForm;
 		TypeCacheEntry *type;
 
-		if (!IsA(expr, ColumnRef))
+		/*
+		 * XXX How could we get anything else than a StatsElem, given the
+		 * grammar? But let's keep it as a safety, maybe shall we turn it
+		 * into an assert?
+		 */
+		if (!IsA(expr, StatsElem))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only simple column references are allowed in CREATE STATISTICS")));
-		cref = (ColumnRef *) expr;
+					 errmsg("only simple column references and expressions are allowed in CREATE STATISTICS")));
 
-		if (list_length(cref->fields) != 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only simple column references are allowed in CREATE STATISTICS")));
-		attname = strVal((Value *) linitial(cref->fields));
+		selem = (StatsElem *) expr;
 
-		atttuple = SearchSysCacheAttName(relid, attname);
-		if (!HeapTupleIsValid(atttuple))
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column \"%s\" does not exist",
-							attname)));
-		attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
+		if (selem->name)	/* column reference */
+		{
+			char	   *attname;
+			attname = selem->name;
+
+			atttuple = SearchSysCacheAttName(relid, attname);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" does not exist",
+								attname)));
+			attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			/* Disallow use of system attributes in extended stats */
+			if (attForm->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("statistics creation on system columns is not supported")));
+
+			/* Disallow data types without a less-than operator */
+			type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
+			if (type->lt_opr == InvalidOid)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
+								attname, format_type_be(attForm->atttypid))));
+
+			attnums[nattnums] = attForm->attnum;
+			nattnums++;
+			ReleaseSysCache(atttuple);
+		}
+		else	/* expression */
+		{
+			Node	   *expr = selem->expr;
+			Oid			atttype;
+
+			Assert(expr != NULL);
+
+			/*
+			 * Disallow data types without a less-than operator.
+			 *
+			 * We ignore this for statistics on a single expression, in
+			 * which case we'll build the regular statistics only (and
+			 * that code can deal with such data types).
+			 */
+			if (list_length(stmt->exprs) > 1)
+			{
+				atttype = exprType(expr);
+				type = lookup_type_cache(atttype, TYPECACHE_LT_OPR);
+				if (type->lt_opr == InvalidOid)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("expression cannot be used in statistics because its type %s has no default btree operator class",
+									format_type_be(atttype))));
+			}
+
+			stxexprs = lappend(stxexprs, expr);
+		}
+	}
 
-		/* Disallow use of system attributes in extended stats */
-		if (attForm->attnum <= 0)
+	/*
+	 * Parse the statistics kinds.  Firstly, check that this is not the
+	 * variant building statistics for a single expression, in which case
+	 * we don't allow specifying any statistis kinds.  The simple variant
+	 * only has one expression, and does not allow statistics kinds.
+	 */
+	if ((list_length(stmt->exprs) == 1) && (list_length(stxexprs) == 1))
+	{
+		/* statistics kinds not specified */
+		if (list_length(stmt->stat_types) > 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("statistics creation on system columns is not supported")));
+					 errmsg("when building statistics on a single expression, statistics kinds may not be specified")));
+	}
 
-		/* Disallow data types without a less-than operator */
-		type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
-		if (type->lt_opr == InvalidOid)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
-							attname, format_type_be(attForm->atttypid))));
+	/* OK, let's check that we recognize the statistics kinds. */
+	build_ndistinct = false;
+	build_dependencies = false;
+	build_mcv = false;
+	foreach(cell, stmt->stat_types)
+	{
+		char	   *type = strVal((Value *) lfirst(cell));
 
-		/* Make sure no more than STATS_MAX_DIMENSIONS columns are used */
-		if (numcols >= STATS_MAX_DIMENSIONS)
+		if (strcmp(type, "ndistinct") == 0)
+		{
+			build_ndistinct = true;
+			requested_type = true;
+		}
+		else if (strcmp(type, "dependencies") == 0)
+		{
+			build_dependencies = true;
+			requested_type = true;
+		}
+		else if (strcmp(type, "mcv") == 0)
+		{
+			build_mcv = true;
+			requested_type = true;
+		}
+		else
 			ereport(ERROR,
-					(errcode(ERRCODE_TOO_MANY_COLUMNS),
-					 errmsg("cannot have more than %d columns in statistics",
-							STATS_MAX_DIMENSIONS)));
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("unrecognized statistics kind \"%s\"",
+							type)));
+	}
 
-		attnums[numcols] = attForm->attnum;
-		numcols++;
-		ReleaseSysCache(atttuple);
+	/*
+	 * If no statistic type was specified, build them all (but request
+	 * expression stats only when there actually are any expressions).
+	 */
+	if (!requested_type)
+	{
+		build_ndistinct = (numcols >= 2);
+		build_dependencies = (numcols >= 2);
+		build_mcv = (numcols >= 2);
 	}
 
 	/*
-	 * Check that at least two columns were specified in the statement. The
-	 * upper bound was already checked in the loop above.
+	 * When there are non-trivial expressions, build the expression stats
+	 * automatically. This allows calculating good estimates for stats that
+	 * consider per-clause estimates (e.g. functional dependencies).
 	 */
-	if (numcols < 2)
+	build_expressions = (list_length(stxexprs) > 0);
+
+	/*
+	 * Check that at least two columns were specified in the statement, or
+	 * that we're building statistics on a single expression.
+	 */
+	if ((numcols < 2) && (list_length(stxexprs) != 1))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("extended statistics require at least 2 columns")));
@@ -258,13 +369,13 @@ CreateStatistics(CreateStatsStmt *stmt)
 	 * it does not hurt (it does not affect the efficiency, unlike for
 	 * indexes, for example).
 	 */
-	qsort(attnums, numcols, sizeof(int16), compare_int16);
+	qsort(attnums, nattnums, sizeof(int16), compare_int16);
 
 	/*
 	 * Check for duplicates in the list of columns. The attnums are sorted so
 	 * just check consecutive elements.
 	 */
-	for (i = 1; i < numcols; i++)
+	for (i = 1; i < nattnums; i++)
 	{
 		if (attnums[i] == attnums[i - 1])
 			ereport(ERROR,
@@ -272,48 +383,36 @@ CreateStatistics(CreateStatsStmt *stmt)
 					 errmsg("duplicate column name in statistics definition")));
 	}
 
-	/* Form an int2vector representation of the sorted column list */
-	stxkeys = buildint2vector(attnums, numcols);
-
 	/*
-	 * Parse the statistics kinds.
+	 * Check for duplicate expressions. We do two loops, counting the
+	 * occurrences of each expression. This is O(N^2) but we only allow
+	 * small number of expressions and it's not executed often.
 	 */
-	build_ndistinct = false;
-	build_dependencies = false;
-	build_mcv = false;
-	foreach(cell, stmt->stat_types)
+	foreach (cell, stxexprs)
 	{
-		char	   *type = strVal((Value *) lfirst(cell));
+		Node   *expr1 = (Node *) lfirst(cell);
+		int		cnt = 0;
 
-		if (strcmp(type, "ndistinct") == 0)
+		foreach (cell2, stxexprs)
 		{
-			build_ndistinct = true;
-			requested_type = true;
-		}
-		else if (strcmp(type, "dependencies") == 0)
-		{
-			build_dependencies = true;
-			requested_type = true;
-		}
-		else if (strcmp(type, "mcv") == 0)
-		{
-			build_mcv = true;
-			requested_type = true;
+			Node   *expr2 = (Node *) lfirst(cell2);
+
+			if (equal(expr1, expr2))
+				cnt += 1;
 		}
-		else
+
+		/* every expression should find at least itself */
+		Assert(cnt >= 1);
+
+		if (cnt > 1)
 			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("unrecognized statistics kind \"%s\"",
-							type)));
-	}
-	/* If no statistic type was specified, build them all. */
-	if (!requested_type)
-	{
-		build_ndistinct = true;
-		build_dependencies = true;
-		build_mcv = true;
+					(errcode(ERRCODE_DUPLICATE_COLUMN),
+					 errmsg("duplicate expression in statistics definition")));
 	}
 
+	/* Form an int2vector representation of the sorted column list */
+	stxkeys = buildint2vector(attnums, nattnums);
+
 	/* construct the char array of enabled statistic types */
 	ntypes = 0;
 	if (build_ndistinct)
@@ -322,9 +421,23 @@ CreateStatistics(CreateStatsStmt *stmt)
 		types[ntypes++] = CharGetDatum(STATS_EXT_DEPENDENCIES);
 	if (build_mcv)
 		types[ntypes++] = CharGetDatum(STATS_EXT_MCV);
+	if (build_expressions)
+		types[ntypes++] = CharGetDatum(STATS_EXT_EXPRESSIONS);
 	Assert(ntypes > 0 && ntypes <= lengthof(types));
 	stxkind = construct_array(types, ntypes, CHAROID, 1, true, TYPALIGN_CHAR);
 
+	/* convert the expressions (if any) to a text datum */
+	if (stxexprs != NIL)
+	{
+		char	   *exprsString;
+
+		exprsString = nodeToString(stxexprs);
+		exprsDatum = CStringGetTextDatum(exprsString);
+		pfree(exprsString);
+	}
+	else
+		exprsDatum = (Datum) 0;
+
 	statrel = table_open(StatisticExtRelationId, RowExclusiveLock);
 
 	/*
@@ -344,6 +457,10 @@ CreateStatistics(CreateStatsStmt *stmt)
 	values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
 	values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind);
 
+	values[Anum_pg_statistic_ext_stxexprs - 1] = exprsDatum;
+	if (exprsDatum == (Datum) 0)
+		nulls[Anum_pg_statistic_ext_stxexprs - 1] = true;
+
 	/* insert it into pg_statistic_ext */
 	htup = heap_form_tuple(statrel->rd_att, values, nulls);
 	CatalogTupleInsert(statrel, htup);
@@ -366,6 +483,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 	datanulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
 	datanulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
 	datanulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
+	datanulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
 
 	/* insert it into pg_statistic_ext_data */
 	htup = heap_form_tuple(datarel->rd_att, datavalues, datanulls);
@@ -389,12 +507,39 @@ CreateStatistics(CreateStatsStmt *stmt)
 	 */
 	ObjectAddressSet(myself, StatisticExtRelationId, statoid);
 
-	for (i = 0; i < numcols; i++)
+	/* add dependencies for plain column references */
+	for (i = 0; i < nattnums; i++)
 	{
 		ObjectAddressSubSet(parentobject, RelationRelationId, relid, attnums[i]);
 		recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
 	}
 
+	/*
+	 * If there are no simply-referenced columns, give the statistics an
+	 * auto dependency on the whole table.  In most cases, this will
+	 * be redundant, but it might not be if the statistics expressions
+	 * contain no Vars (which might seem strange but possible).
+	 *
+	 * XXX This is copied from index_create, not sure if it's applicable
+	 * to extended statistics too.
+	 */
+	if (!nattnums)
+	{
+		ObjectAddressSet(parentobject, RelationRelationId, relid);
+		recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
+	}
+
+	/*
+	 * Store dependencies on anything mentioned in statistics expressions,
+	 * just like we do for index expressions.
+	 */
+	if (stxexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) stxexprs,
+										relid,
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, false, true);
+
 	/*
 	 * Also add dependencies on namespace and owner.  These are required
 	 * because the stats object might have a different namespace and/or owner
@@ -638,6 +783,7 @@ UpdateStatisticsForTypeChange(Oid statsOid, Oid relationOid, int attnum,
 
 	replaces[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
 	nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
+	nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
 
 	rel = table_open(StatisticExtDataRelationId, RowExclusiveLock);
 
@@ -724,18 +870,26 @@ ChooseExtendedStatisticNameAddition(List *exprs)
 	buf[0] = '\0';
 	foreach(lc, exprs)
 	{
-		ColumnRef  *cref = (ColumnRef *) lfirst(lc);
+		StatsElem  *selem = (StatsElem *) lfirst(lc);
 		const char *name;
 
 		/* It should be one of these, but just skip if it happens not to be */
-		if (!IsA(cref, ColumnRef))
+		if (!IsA(selem, StatsElem))
 			continue;
 
-		name = strVal((Value *) linitial(cref->fields));
+		name = selem->name;
 
 		if (buflen > 0)
 			buf[buflen++] = '_';	/* insert _ between names */
 
+		/*
+		 * FIXME use 'expr' for expressions, which have empty column names.
+		 * For indexes this is handled in ChooseIndexColumnNames, but we
+		 * have no such function for stats.
+		 */
+		if (!name)
+			name = "expr";
+
 		/*
 		 * At this point we have buflen <= NAMEDATALEN.  name should be less
 		 * than NAMEDATALEN already, but use strlcpy for paranoia.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ba3ccc712c..a21be7ffb1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2925,6 +2925,17 @@ _copyIndexElem(const IndexElem *from)
 	return newnode;
 }
 
+static StatsElem *
+_copyStatsElem(const StatsElem *from)
+{
+	StatsElem  *newnode = makeNode(StatsElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+
+	return newnode;
+}
+
 static ColumnDef *
 _copyColumnDef(const ColumnDef *from)
 {
@@ -5636,6 +5647,9 @@ copyObjectImpl(const void *from)
 		case T_IndexElem:
 			retval = _copyIndexElem(from);
 			break;
+		case T_StatsElem:
+			retval = _copyStatsElem(from);
+			break;
 		case T_ColumnDef:
 			retval = _copyColumnDef(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a2ef853dc2..2a5421c10f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2593,6 +2593,16 @@ _equalIndexElem(const IndexElem *a, const IndexElem *b)
 	return true;
 }
 
+
+static bool
+_equalStatsElem(const StatsElem *a, const StatsElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+
+	return true;
+}
+
 static bool
 _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
 {
@@ -3689,6 +3699,9 @@ equal(const void *a, const void *b)
 		case T_IndexElem:
 			retval = _equalIndexElem(a, b);
 			break;
+		case T_StatsElem:
+			retval = _equalStatsElem(a, b);
+			break;
 		case T_ColumnDef:
 			retval = _equalColumnDef(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8392be6d44..956e8d8151 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2932,6 +2932,15 @@ _outIndexElem(StringInfo str, const IndexElem *node)
 	WRITE_ENUM_FIELD(nulls_ordering, SortByNulls);
 }
 
+static void
+_outStatsElem(StringInfo str, const StatsElem *node)
+{
+	WRITE_NODE_TYPE("STATSELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+}
+
 static void
 _outQuery(StringInfo str, const Query *node)
 {
@@ -4241,6 +4250,9 @@ outNode(StringInfo str, const void *obj)
 			case T_IndexElem:
 				_outIndexElem(str, obj);
 				break;
+			case T_StatsElem:
+				_outStatsElem(str, obj);
+				break;
 			case T_Query:
 				_outQuery(str, obj);
 				break;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index da322b453e..1e64d52c83 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,6 +34,7 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
@@ -1302,6 +1303,7 @@ get_relation_constraints(PlannerInfo *root,
 static List *
 get_relation_statistics(RelOptInfo *rel, Relation relation)
 {
+	Index		varno = rel->relid;
 	List	   *statoidlist;
 	List	   *stainfos = NIL;
 	ListCell   *l;
@@ -1316,6 +1318,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
 		HeapTuple	dtup;
 		Bitmapset  *keys = NULL;
 		int			i;
+		List	   *exprs = NIL;
 
 		htup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statOid));
 		if (!HeapTupleIsValid(htup))
@@ -1334,6 +1337,49 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
 		for (i = 0; i < staForm->stxkeys.dim1; i++)
 			keys = bms_add_member(keys, staForm->stxkeys.values[i]);
 
+		/*
+		 * preprocess expression (if any)
+		 *
+		 * FIXME Should we cache the result somewhere?
+		 */
+		{
+			bool		isnull;
+			Datum		datum;
+
+			/* decode expression (if any) */
+			datum = SysCacheGetAttr(STATEXTOID, htup,
+									Anum_pg_statistic_ext_stxexprs, &isnull);
+
+			if (!isnull)
+			{
+				char *exprsString;
+
+				exprsString = TextDatumGetCString(datum);
+				exprs = (List *) stringToNode(exprsString);
+				pfree(exprsString);
+
+				/*
+				 * Run the expressions through eval_const_expressions. This is not just an
+				 * optimization, but is necessary, because the planner will be comparing
+				 * them to similarly-processed qual clauses, and may fail to detect valid
+				 * matches without this.  We must not use canonicalize_qual, however,
+				 * since these aren't qual expressions.
+				 */
+				exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
+
+				/* May as well fix opfuncids too */
+				fix_opfuncids((Node *) exprs);
+
+				/*
+				 * Modify the copies we obtain from the relcache to have the
+				 * correct varno for the parent relation, so that they match up
+				 * correctly against qual clauses.
+				 */
+				if (varno != 1)
+					ChangeVarNodes((Node *) exprs, 1, varno, 0);
+			}
+		}
+
 		/* add one StatisticExtInfo for each kind built */
 		if (statext_is_kind_built(dtup, STATS_EXT_NDISTINCT))
 		{
@@ -1343,6 +1389,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
 			info->rel = rel;
 			info->kind = STATS_EXT_NDISTINCT;
 			info->keys = bms_copy(keys);
+			info->exprs = exprs;
 
 			stainfos = lappend(stainfos, info);
 		}
@@ -1355,6 +1402,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
 			info->rel = rel;
 			info->kind = STATS_EXT_DEPENDENCIES;
 			info->keys = bms_copy(keys);
+			info->exprs = exprs;
 
 			stainfos = lappend(stainfos, info);
 		}
@@ -1367,6 +1415,20 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
 			info->rel = rel;
 			info->kind = STATS_EXT_MCV;
 			info->keys = bms_copy(keys);
+			info->exprs = exprs;
+
+			stainfos = lappend(stainfos, info);
+		}
+
+		if (statext_is_kind_built(dtup, STATS_EXT_EXPRESSIONS))
+		{
+			StatisticExtInfo *info = makeNode(StatisticExtInfo);
+
+			info->statOid = statOid;
+			info->rel = rel;
+			info->kind = STATS_EXT_EXPRESSIONS;
+			info->keys = bms_copy(keys);
+			info->exprs = exprs;
 
 			stainfos = lappend(stainfos, info);
 		}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..d219976b53 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -232,6 +232,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	WindowDef			*windef;
 	JoinExpr			*jexpr;
 	IndexElem			*ielem;
+	StatsElem			*selem;
 	Alias				*alias;
 	RangeVar			*range;
 	IntoClause			*into;
@@ -396,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				old_aggr_definition old_aggr_list
 				oper_argtypes RuleActionList RuleActionMulti
 				opt_column_list columnList opt_name_list
-				sort_clause opt_sort_clause sortby_list index_params
+				sort_clause opt_sort_clause sortby_list index_params stats_params
 				opt_include opt_c_include index_including_params
 				name_list role_list from_clause from_list opt_array_bounds
 				qualified_name_list any_name any_name_list type_name_list
@@ -500,6 +501,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>	func_alias_clause
 %type <sortby>	sortby
 %type <ielem>	index_elem index_elem_options
+%type <selem>	stats_param
 %type <node>	table_ref
 %type <jexpr>	joined_table
 %type <range>	relation_expr
@@ -4049,7 +4051,7 @@ ExistingIndex:   USING INDEX name					{ $$ = $3; }
 
 CreateStatsStmt:
 			CREATE STATISTICS any_name
-			opt_name_list ON expr_list FROM from_list
+			opt_name_list ON stats_params FROM from_list
 				{
 					CreateStatsStmt *n = makeNode(CreateStatsStmt);
 					n->defnames = $3;
@@ -4061,7 +4063,7 @@ CreateStatsStmt:
 					$$ = (Node *)n;
 				}
 			| CREATE STATISTICS IF_P NOT EXISTS any_name
-			opt_name_list ON expr_list FROM from_list
+			opt_name_list ON stats_params FROM from_list
 				{
 					CreateStatsStmt *n = makeNode(CreateStatsStmt);
 					n->defnames = $6;
@@ -4074,6 +4076,36 @@ CreateStatsStmt:
 				}
 			;
 
+/*
+ * Statistics attributes can be either simple column references, or arbitrary
+ * expressions in parens.  For compatibility with index attributes permitted
+ * in CREATE INDEX, we allow an expression that's just a function call to be
+ * written without parens.
+ */
+
+stats_params:	stats_param							{ $$ = list_make1($1); }
+			| stats_params ',' stats_param			{ $$ = lappend($1, $3); }
+		;
+
+stats_param:	ColId
+				{
+					$$ = makeNode(StatsElem);
+					$$->name = $1;
+					$$->expr = NULL;
+				}
+			| func_expr_windowless
+				{
+					$$ = makeNode(StatsElem);
+					$$->name = NULL;
+					$$->expr = $1;
+				}
+			| '(' a_expr ')'
+				{
+					$$ = makeNode(StatsElem);
+					$$->name = NULL;
+					$$->expr = $2;
+				}
+		;
 
 /*****************************************************************************
  *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 588f005dd9..0b0841afb9 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -484,6 +484,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			else
 				err = _("grouping operations are not allowed in index predicates");
 
+			break;
+		case EXPR_KIND_STATS_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in statistics expressions");
+			else
+				err = _("grouping operations are not allowed in statistics expressions");
+
 			break;
 		case EXPR_KIND_ALTER_COL_TRANSFORM:
 			if (isAgg)
@@ -906,6 +913,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_INDEX_EXPRESSION:
 			err = _("window functions are not allowed in index expressions");
 			break;
+		case EXPR_KIND_STATS_EXPRESSION:
+			err = _("window functions are not allowed in statistics expressions");
+			break;
 		case EXPR_KIND_INDEX_PREDICATE:
 			err = _("window functions are not allowed in index predicates");
 			break;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 379355f9bf..fcc1bb33d1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -500,6 +500,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_FUNCTION_DEFAULT:
 		case EXPR_KIND_INDEX_EXPRESSION:
 		case EXPR_KIND_INDEX_PREDICATE:
+		case EXPR_KIND_STATS_EXPRESSION:
 		case EXPR_KIND_ALTER_COL_TRANSFORM:
 		case EXPR_KIND_EXECUTE_PARAMETER:
 		case EXPR_KIND_TRIGGER_WHEN:
@@ -1739,6 +1740,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_INDEX_PREDICATE:
 			err = _("cannot use subquery in index predicate");
 			break;
+		case EXPR_KIND_STATS_EXPRESSION:
+			err = _("cannot use subquery in statistics expression");
+			break;
 		case EXPR_KIND_ALTER_COL_TRANSFORM:
 			err = _("cannot use subquery in transform expression");
 			break;
@@ -3028,6 +3032,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "index expression";
 		case EXPR_KIND_INDEX_PREDICATE:
 			return "index predicate";
+		case EXPR_KIND_STATS_EXPRESSION:
+			return "statistics expression";
 		case EXPR_KIND_ALTER_COL_TRANSFORM:
 			return "USING";
 		case EXPR_KIND_EXECUTE_PARAMETER:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 07d0013e84..652930ddf9 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2503,6 +2503,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_INDEX_PREDICATE:
 			err = _("set-returning functions are not allowed in index predicates");
 			break;
+		case EXPR_KIND_STATS_EXPRESSION:
+			err = _("set-returning functions are not allowed in statistics expressions");
+			break;
 		case EXPR_KIND_ALTER_COL_TRANSFORM:
 			err = _("set-returning functions are not allowed in transform expressions");
 			break;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b31f3afa03..0028240d1a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1898,6 +1898,9 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
 			stat_types = lappend(stat_types, makeString("dependencies"));
 		else if (enabled[i] == STATS_EXT_MCV)
 			stat_types = lappend(stat_types, makeString("mcv"));
+		else if (enabled[i] == STATS_EXT_EXPRESSIONS)
+			/* expression stats are not exposed to users */
+			continue;
 		else
 			elog(ERROR, "unrecognized statistics kind %c", enabled[i]);
 	}
@@ -1905,14 +1908,43 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
 	/* Determine which columns the statistics are on */
 	for (i = 0; i < statsrec->stxkeys.dim1; i++)
 	{
-		ColumnRef  *cref = makeNode(ColumnRef);
+		StatsElem  *selem = makeNode(StatsElem);
 		AttrNumber	attnum = statsrec->stxkeys.values[i];
 
-		cref->fields = list_make1(makeString(get_attname(heapRelid,
-														 attnum, false)));
-		cref->location = -1;
+		selem->name = get_attname(heapRelid, attnum, false);
+		selem->expr = NULL;
 
-		def_names = lappend(def_names, cref);
+		def_names = lappend(def_names, selem);
+	}
+
+	/*
+	 * Now handle expressions, if there are any.  The order does not
+	 * matter for extended stats, so we simply append them after
+	 * simple column references.
+	 */
+	datum = SysCacheGetAttr(STATEXTOID, ht_stats,
+							Anum_pg_statistic_ext_stxexprs, &isnull);
+
+	if (!isnull)
+	{
+		ListCell   *lc;
+		List	   *exprs = NIL;
+		char	   *exprsString;
+
+		exprsString = TextDatumGetCString(datum);
+		exprs = (List *) stringToNode(exprsString);
+
+		foreach(lc, exprs)
+		{
+			StatsElem  *selem = makeNode(StatsElem);
+
+			selem->name = NULL;
+			selem->expr = (Node *) lfirst(lc);
+
+			def_names = lappend(def_names, selem);
+		}
+
+		pfree(exprsString);
 	}
 
 	/* finally, build the output node */
@@ -1923,6 +1955,7 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
 	stats->relations = list_make1(heapRel);
 	stats->stxcomment = NULL;
 	stats->if_not_exists = false;
+	stats->transformed = true;	/* don't need transformStatsStmt */
 
 	/* Clean up */
 	ReleaseSysCache(ht_stats);
@@ -2847,6 +2880,84 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
 	return stmt;
 }
 
+/*
+ * transformStatsStmt - parse analysis for CREATE STATISTICS
+ *
+ * To avoid race conditions, it's important that this function rely only on
+ * the passed-in relid (and not on stmt->relation) to determine the target
+ * relation.
+ */
+CreateStatsStmt *
+transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+{
+	ParseState *pstate;
+	ParseNamespaceItem *nsitem;
+	ListCell   *l;
+	Relation	rel;
+
+	/* Nothing to do if statement already transformed. */
+	if (stmt->transformed)
+		return stmt;
+
+	/*
+	 * We must not scribble on the passed-in CreateStatsStmt, so copy it.  (This is
+	 * overkill, but easy.)
+	 */
+	stmt = copyObject(stmt);
+
+	/* Set up pstate */
+	pstate = make_parsestate(NULL);
+	pstate->p_sourcetext = queryString;
+
+	/*
+	 * Put the parent table into the rtable so that the expressions can refer
+	 * to its fields without qualification.  Caller is responsible for locking
+	 * relation, but we still need to open it.
+	 */
+	rel = relation_open(relid, NoLock);
+	nsitem = addRangeTableEntryForRelation(pstate, rel,
+										   AccessShareLock,
+										   NULL, false, true);
+
+	/* no to join list, yes to namespaces */
+	addNSItemToQuery(pstate, nsitem, false, true, true);
+
+	/* take care of any expressions */
+	foreach(l, stmt->exprs)
+	{
+		StatsElem  *selem = (StatsElem *) lfirst(l);
+
+		if (selem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			selem->expr = transformExpr(pstate, selem->expr,
+										EXPR_KIND_STATS_EXPRESSION);
+
+			/* We have to fix its collations too */
+			assign_expr_collations(pstate, selem->expr);
+		}
+	}
+
+	/*
+	 * Check that only the base rel is mentioned.  (This should be dead code
+	 * now that add_missing_from is history.)
+	 */
+	if (list_length(pstate->p_rtable) != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+				 errmsg("statistics expressions and predicates can refer only to the table being indexed")));
+
+	free_parsestate(pstate);
+
+	/* Close relation */
+	table_close(rel, NoLock);
+
+	/* Mark statement as successfully transformed */
+	stmt->transformed = true;
+
+	return stmt;
+}
+
 
 /*
  * transformRuleStmt -
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index f6e399b192..d52ce11d3f 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -70,15 +70,18 @@ static void generate_dependencies(DependencyGenerator state);
 static DependencyGenerator DependencyGenerator_init(int n, int k);
 static void DependencyGenerator_free(DependencyGenerator state);
 static AttrNumber *DependencyGenerator_next(DependencyGenerator state);
-static double dependency_degree(int numrows, HeapTuple *rows, int k,
-								AttrNumber *dependency, VacAttrStats **stats, Bitmapset *attrs);
+static double dependency_degree(int numrows, HeapTuple *rows,
+								ExprInfo *exprs, int k,
+								AttrNumber *dependency, VacAttrStats **stats,
+								Bitmapset *attrs);
 static bool dependency_is_fully_matched(MVDependency *dependency,
 										Bitmapset *attnums);
 static bool dependency_is_compatible_clause(Node *clause, Index relid,
 											AttrNumber *attnum);
+static bool dependency_is_compatible_expression(Node *clause, Index relid,
+												List *statlist, Node **expr);
 static MVDependency *find_strongest_dependency(MVDependencies **dependencies,
-											   int ndependencies,
-											   Bitmapset *attnums);
+						  int ndependencies, Bitmapset *attnums);
 static Selectivity clauselist_apply_dependencies(PlannerInfo *root, List *clauses,
 												 int varRelid, JoinType jointype,
 												 SpecialJoinInfo *sjinfo,
@@ -219,8 +222,9 @@ DependencyGenerator_next(DependencyGenerator state)
  * the last one.
  */
 static double
-dependency_degree(int numrows, HeapTuple *rows, int k, AttrNumber *dependency,
-				  VacAttrStats **stats, Bitmapset *attrs)
+dependency_degree(int numrows, HeapTuple *rows, ExprInfo *exprs, int k,
+				  AttrNumber *dependency, VacAttrStats **stats,
+				  Bitmapset *attrs)
 {
 	int			i,
 				nitems;
@@ -289,8 +293,8 @@ dependency_degree(int numrows, HeapTuple *rows, int k, AttrNumber *dependency,
 	 * descriptor.  For now that assumption holds, but it might change in the
 	 * future for example if we support statistics on multiple tables.
 	 */
-	items = build_sorted_items(numrows, &nitems, rows, stats[0]->tupDesc,
-							   mss, k, attnums_dep);
+	items = build_sorted_items(numrows, &nitems, rows, exprs,
+							   stats[0]->tupDesc, mss, k, attnums_dep);
 
 	/*
 	 * Walk through the sorted array, split it into rows according to the
@@ -360,7 +364,8 @@ dependency_degree(int numrows, HeapTuple *rows, int k, AttrNumber *dependency,
  *	   (c) -> b
  */
 MVDependencies *
-statext_dependencies_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
+statext_dependencies_build(int numrows, HeapTuple *rows,
+						   ExprInfo *exprs, Bitmapset *attrs,
 						   VacAttrStats **stats)
 {
 	int			i,
@@ -371,6 +376,9 @@ statext_dependencies_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
 	/* result */
 	MVDependencies *dependencies = NULL;
 
+	/* treat expressions as special attributes with high attnums */
+	attrs = add_expressions_to_attributes(attrs, exprs->nexprs);
+
 	/*
 	 * Transform the bms into an array, to make accessing i-th member easier.
 	 */
@@ -398,7 +406,8 @@ statext_dependencies_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
 			MVDependency *d;
 
 			/* compute how valid the dependency seems */
-			degree = dependency_degree(numrows, rows, k, dependency, stats, attrs);
+			degree = dependency_degree(numrows, rows, exprs, k, dependency,
+									   stats, attrs);
 
 			/*
 			 * if the dependency seems entirely invalid, don't store it
@@ -441,6 +450,8 @@ statext_dependencies_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
 		DependencyGenerator_free(DependencyGenerator);
 	}
 
+	pfree(attrs);
+
 	return dependencies;
 }
 
@@ -603,6 +614,7 @@ static bool
 dependency_is_fully_matched(MVDependency *dependency, Bitmapset *attnums)
 {
 	int			j;
+	bool		result = true;	/* match by default */
 
 	/*
 	 * Check that the dependency actually is fully covered by clauses. We have
@@ -613,10 +625,13 @@ dependency_is_fully_matched(MVDependency *dependency, Bitmapset *attnums)
 		int			attnum = dependency->attributes[j];
 
 		if (!bms_is_member(attnum, attnums))
-			return false;
+		{
+			result = false;
+			break;
+		}
 	}
 
-	return true;
+	return result;
 }
 
 /*
@@ -927,8 +942,8 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum)
  * (see the comment in dependencies_clauselist_selectivity).
  */
 static MVDependency *
-find_strongest_dependency(MVDependencies **dependencies, int ndependencies,
-						  Bitmapset *attnums)
+find_strongest_dependency(MVDependencies **dependencies,
+						  int ndependencies, Bitmapset *attnums)
 {
 	int			i,
 				j;
@@ -1157,6 +1172,131 @@ clauselist_apply_dependencies(PlannerInfo *root, List *clauses,
 	return s1;
 }
 
+/*
+ * Similar to dependency_is_compatible_clause, but don't enforce that the
+ * expression is a simple Var. OTOH we check that there's at least one
+ * statistics matching the expression.
+ */
+static bool
+dependency_is_compatible_expression(Node *clause, Index relid, List *statlist, Node **expr)
+{
+	List	   *vars;
+	ListCell   *lc, *lc2;
+
+	RestrictInfo *rinfo = (RestrictInfo *) clause;
+	Node		   *clause_expr;
+
+	if (!IsA(rinfo, RestrictInfo))
+		return false;
+
+	/* Pseudoconstants are not interesting (they couldn't contain a Var) */
+	if (rinfo->pseudoconstant)
+		return false;
+
+	/* Clauses referencing multiple, or no, varnos are incompatible */
+	if (bms_membership(rinfo->clause_relids) != BMS_SINGLETON)
+		return false;
+
+	if (is_opclause(rinfo->clause))
+	{
+		/* If it's an opclause, check for Var = Const or Const = Var. */
+		OpExpr	   *expr = (OpExpr *) rinfo->clause;
+
+		/* Only expressions with two arguments are candidates. */
+		if (list_length(expr->args) != 2)
+			return false;
+
+		/* Make sure non-selected argument is a pseudoconstant. */
+		if (is_pseudo_constant_clause(lsecond(expr->args)))
+			clause_expr = linitial(expr->args);
+		else if (is_pseudo_constant_clause(linitial(expr->args)))
+			clause_expr = lsecond(expr->args);
+		else
+			return false;
+
+		/*
+		 * If it's not an "=" operator, just ignore the clause, as it's not
+		 * compatible with functional dependencies.
+		 *
+		 * This uses the function for estimating selectivity, not the operator
+		 * directly (a bit awkward, but well ...).
+		 *
+		 * XXX this is pretty dubious; probably it'd be better to check btree
+		 * or hash opclass membership, so as not to be fooled by custom
+		 * selectivity functions, and to be more consistent with decisions
+		 * elsewhere in the planner.
+		 */
+		if (get_oprrest(expr->opno) != F_EQSEL)
+			return false;
+
+		/* OK to proceed with checking "var" */
+	}
+	else if (is_notclause(rinfo->clause))
+	{
+		/*
+		 * "NOT x" can be interpreted as "x = false", so get the argument and
+		 * proceed with seeing if it's a suitable Var.
+		 */
+		clause_expr = (Node *) get_notclausearg(rinfo->clause);
+	}
+	else
+	{
+		/*
+		 * A boolean expression "x" can be interpreted as "x = true", so
+		 * proceed with seeing if it's a suitable Var.
+		 */
+		clause_expr = (Node *) rinfo->clause;
+	}
+
+	/*
+	 * We may ignore any RelabelType node above the operand.  (There won't be
+	 * more than one, since eval_const_expressions has been applied already.)
+	 */
+	if (IsA(clause_expr, RelabelType))
+		clause_expr = (Node *) ((RelabelType *) clause_expr)->arg;
+
+	vars = pull_var_clause(clause_expr, 0);
+
+	foreach (lc, vars)
+	{
+		Var *var = (Var *) lfirst(lc);
+
+		/* Ensure Var is from the correct relation */
+		if (var->varno != relid)
+			return false;
+
+		/* We also better ensure the Var is from the current level */
+		if (var->varlevelsup != 0)
+			return false;
+
+		/* Also ignore system attributes (we don't allow stats on those) */
+		if (!AttrNumberIsForUserDefinedAttr(var->varattno))
+			return false;
+	}
+
+	foreach (lc, statlist)
+	{
+		StatisticExtInfo *info = (StatisticExtInfo *) lfirst(lc);
+
+		/* ignore stats without dependencies */
+		if (info->kind != STATS_EXT_DEPENDENCIES)
+			continue;
+
+		foreach (lc2, info->exprs)
+		{
+			Node *stat_expr = (Node *) lfirst(lc2);
+
+			if (equal(clause_expr, stat_expr))
+			{
+				*expr = stat_expr;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
 /*
  * dependencies_clauselist_selectivity
  *		Return the estimated selectivity of (a subset of) the given clauses
@@ -1205,6 +1345,10 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
 	int			ndependencies;
 	int			i;
 
+	/* unique expressions */
+	Node	  **unique_exprs;
+	int			unique_exprs_cnt;
+
 	/* check if there's any stats that might be useful for us. */
 	if (!has_stats_of_kind(rel->statlist, STATS_EXT_DEPENDENCIES))
 		return 1.0;
@@ -1212,6 +1356,10 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
 	list_attnums = (AttrNumber *) palloc(sizeof(AttrNumber) *
 										 list_length(clauses));
 
+	/* unique expressions */
+	unique_exprs = (Node **) palloc(sizeof(Node *) * list_length(clauses));
+	unique_exprs_cnt = 0;
+
 	/*
 	 * Pre-process the clauses list to extract the attnums seen in each item.
 	 * We need to determine if there's any clauses which will be useful for
@@ -1222,29 +1370,70 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
 	 *
 	 * We also skip clauses that we already estimated using different types of
 	 * statistics (we treat them as incompatible).
+	 *
+	 * For expressions, we generate attnums higher than MaxHeapAttributeNumber
+	 * so that we can work with attnums only.
 	 */
 	listidx = 0;
 	foreach(l, clauses)
 	{
 		Node	   *clause = (Node *) lfirst(l);
 		AttrNumber	attnum;
+		Node	   *expr = NULL;
+
+		/* ignore clause by default */
+		list_attnums[listidx] = InvalidAttrNumber;
 
-		if (!bms_is_member(listidx, *estimatedclauses) &&
-			dependency_is_compatible_clause(clause, rel->relid, &attnum))
+		if (!bms_is_member(listidx, *estimatedclauses))
 		{
-			list_attnums[listidx] = attnum;
-			clauses_attnums = bms_add_member(clauses_attnums, attnum);
+			if (dependency_is_compatible_clause(clause, rel->relid, &attnum))
+			{
+				list_attnums[listidx] = attnum;
+				clauses_attnums = bms_add_member(clauses_attnums, attnum);
+			}
+			else if (dependency_is_compatible_expression(clause, rel->relid,
+														 rel->statlist,
+														 &expr))
+			{
+				/* special attnum assigned to this expression */
+				attnum = InvalidAttrNumber;
+
+				Assert(expr != NULL);
+
+				/* build list of unique expressions, for re-mapping later */
+				for (i = 0; i < unique_exprs_cnt; i++)
+				{
+					if (equal(unique_exprs[i], expr))
+					{
+						attnum = (i + 1);
+						break;
+					}
+				}
+
+				/* not found in the list, so add it */
+				if (attnum == InvalidAttrNumber)
+				{
+					attnum = EXPRESSION_ATTNUM(unique_exprs_cnt);
+					unique_exprs[unique_exprs_cnt++] = expr;
+
+					/* shouldn't have seen this attnum yet */
+					Assert(!bms_is_member(attnum, clauses_attnums));
+				}
+
+				/* we may add the attnum repeatedly to clauses_attnums */
+				clauses_attnums = bms_add_member(clauses_attnums, attnum);
+
+				list_attnums[listidx] = attnum;
+			}
 		}
-		else
-			list_attnums[listidx] = InvalidAttrNumber;
 
 		listidx++;
 	}
 
 	/*
-	 * If there's not at least two distinct attnums then reject the whole list
-	 * of clauses. We must return 1.0 so the calling function's selectivity is
-	 * unaffected.
+	 * If there's not at least two distinct attnums and expressions, then
+	 * reject the whole list of clauses. We must return 1.0 so the calling
+	 * function's selectivity is unaffected.
 	 */
 	if (bms_membership(clauses_attnums) != BMS_MULTIPLE)
 	{
@@ -1273,25 +1462,138 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
 	{
 		StatisticExtInfo *stat = (StatisticExtInfo *) lfirst(l);
 		Bitmapset  *matched;
-		BMS_Membership membership;
+		int			nmatched;
+		int			nexprs;
+		MVDependencies *deps;
 
 		/* skip statistics that are not of the correct type */
 		if (stat->kind != STATS_EXT_DEPENDENCIES)
 			continue;
 
+		/* count matching simple clauses */
 		matched = bms_intersect(clauses_attnums, stat->keys);
-		membership = bms_membership(matched);
+		nmatched = bms_num_members(matched);
 		bms_free(matched);
 
-		/* skip objects matching fewer than two attributes from clauses */
-		if (membership != BMS_MULTIPLE)
+		/* count matching expressions */
+		nexprs = 0;
+		for (i = 0; i < unique_exprs_cnt; i++)
+		{
+			ListCell   *lc;
+
+			foreach (lc, stat->exprs)
+			{
+				Node *stat_expr = (Node *) lfirst(lc);
+
+				/* try to match it */
+				if (equal(stat_expr, unique_exprs[i]))
+					nexprs++;
+			}
+		}
+
+		/*
+		 * Skip objects matching fewer than two attributes/expressions
+		 * from clauses.
+		 */
+		if (nmatched + nexprs < 2)
 			continue;
 
-		func_dependencies[nfunc_dependencies]
-			= statext_dependencies_load(stat->statOid);
+		deps = statext_dependencies_load(stat->statOid);
 
-		total_ndeps += func_dependencies[nfunc_dependencies]->ndeps;
-		nfunc_dependencies++;
+		/*
+		 * The expressions may be represented by different attnums in the
+		 * stats, we need to remap them to be consistent with the clauses.
+		 * That will make the later steps (e.g. picking the strongest item
+		 * and so on) much simpler.
+		 *
+		 * When we're at it, we can also remove dependencies referencing
+		 * missing clauses (i.e. expressions that are not in the clauses).
+		 *
+		 * XXX We might also skip clauses referencing missing attnums, not
+		 * just expressions.
+		 */
+		if (stat->exprs)
+		{
+			int			ndeps = 0;
+
+			for (i = 0; i < deps->ndeps; i++)
+			{
+				bool			skip = false;
+				MVDependency   *dep = deps->deps[i];
+				int				j;
+
+				for (j = 0; j < dep->nattributes; j++)
+				{
+					int			idx;
+					Node	   *expr;
+					int			k;
+					AttrNumber	unique_attnum = InvalidAttrNumber;
+
+					/* regular attribute, no need to remap */
+					if (dep->attributes[j] <= MaxHeapAttributeNumber)
+						continue;
+
+					/* index of the expression */
+					idx = EXPRESSION_INDEX(dep->attributes[j]);
+
+					/* make sure the expression index is valid */
+					Assert((idx >= 0) && (idx < list_length(stat->exprs)));
+
+					expr = (Node *) list_nth(stat->exprs, idx);
+
+					/* try to find the expression in the unique list */
+					for (k = 0; k < unique_exprs_cnt; k++)
+					{
+						/*
+						 * found a matching unique expression, use the attnum
+						 * (derived from index of the unique expression)
+						 */
+						if (equal(unique_exprs[k], expr))
+						{
+							unique_attnum = EXPRESSION_ATTNUM(k);
+							break;
+						}
+					}
+
+					/*
+					 * Not found a matching expression, so we can simply
+					 * skip this dependency, because there's no chance it
+					 * will be fully covered.
+					 */
+					if (unique_attnum == InvalidAttrNumber)
+					{
+						skip = true;
+						break;
+					}
+
+					/* otherwise remap it to the new attnum */
+					dep->attributes[j] = unique_attnum;
+				}
+
+				/* if found a matching, */
+				if (!skip)
+				{
+					/* maybe we've skipped something earlier, so move it */
+					if (ndeps != i)
+						deps->deps[ndeps] = deps->deps[i];
+
+					ndeps++;
+				}
+			}
+
+			deps->ndeps = ndeps;
+		}
+
+		/*
+		 * It's possible we've removed all dependencies, in which case we
+		 * don't bother adding it to the list.
+		 */
+		if (deps->ndeps > 0)
+		{
+			func_dependencies[nfunc_dependencies] = deps;
+			total_ndeps += deps->ndeps;
+			nfunc_dependencies++;
+		}
 	}
 
 	/* if no matching stats could be found then we've nothing to do */
@@ -1300,6 +1602,7 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
 		pfree(func_dependencies);
 		bms_free(clauses_attnums);
 		pfree(list_attnums);
+		pfree(unique_exprs);
 		return 1.0;
 	}
 
@@ -1347,6 +1650,7 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
 	pfree(func_dependencies);
 	bms_free(clauses_attnums);
 	pfree(list_attnums);
+	pfree(unique_exprs);
 
 	return s1;
 }
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index a030ea3653..d63fa527ef 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "executor/executor.h"
 #include "commands/progress.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -35,6 +36,7 @@
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/array.h"
+#include "utils/attoptcache.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -42,6 +44,7 @@
 #include "utils/rel.h"
 #include "utils/selfuncs.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 /*
  * To avoid consuming too much memory during analysis and/or too much space
@@ -66,18 +69,35 @@ typedef struct StatExtEntry
 	Bitmapset  *columns;		/* attribute numbers covered by the object */
 	List	   *types;			/* 'char' list of enabled statistic kinds */
 	int			stattarget;		/* statistics target (-1 for default) */
+	List	   *exprs;			/* expressions */
 } StatExtEntry;
 
 
 static List *fetch_statentries_for_relation(Relation pg_statext, Oid relid);
-static VacAttrStats **lookup_var_attr_stats(Relation rel, Bitmapset *attrs,
+static VacAttrStats **lookup_var_attr_stats(Relation rel, Bitmapset *attrs, List *exprs,
 											int nvacatts, VacAttrStats **vacatts);
-static void statext_store(Oid relid,
+static void statext_store(Oid statOid,
 						  MVNDistinct *ndistinct, MVDependencies *dependencies,
-						  MCVList *mcv, VacAttrStats **stats);
+						  MCVList *mcv, Datum exprs, VacAttrStats **stats);
 static int	statext_compute_stattarget(int stattarget,
 									   int natts, VacAttrStats **stats);
 
+typedef struct AnlExprData
+{
+	Node		   *expr;			/* expression to analyze */
+	VacAttrStats   *vacattrstat;	/* index attrs to analyze */
+} AnlExprData;
+
+static void compute_expr_stats(Relation onerel, double totalrows,
+					AnlExprData *exprdata, int nexprs,
+					HeapTuple *rows, int numrows);
+static Datum serialize_expr_stats(AnlExprData *exprdata, int nexprs);
+static Datum expr_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
+static AnlExprData *build_expr_data(List *exprs);
+static VacAttrStats *examine_expression(Node *expr);
+static ExprInfo *evaluate_expressions(Relation rel, List *exprs,
+									  int numrows, HeapTuple *rows);
+
 /*
  * Compute requested extended stats, using the rows sampled for the plain
  * (single-column) stats.
@@ -92,7 +112,7 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 {
 	Relation	pg_stext;
 	ListCell   *lc;
-	List	   *stats;
+	List	   *statslist;
 	MemoryContext cxt;
 	MemoryContext oldcxt;
 	int64		ext_cnt;
@@ -103,10 +123,10 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 	oldcxt = MemoryContextSwitchTo(cxt);
 
 	pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
-	stats = fetch_statentries_for_relation(pg_stext, RelationGetRelid(onerel));
+	statslist = fetch_statentries_for_relation(pg_stext, RelationGetRelid(onerel));
 
 	/* report this phase */
-	if (stats != NIL)
+	if (statslist != NIL)
 	{
 		const int	index[] = {
 			PROGRESS_ANALYZE_PHASE,
@@ -114,28 +134,31 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 		};
 		const int64 val[] = {
 			PROGRESS_ANALYZE_PHASE_COMPUTE_EXT_STATS,
-			list_length(stats)
+			list_length(statslist)
 		};
 
 		pgstat_progress_update_multi_param(2, index, val);
 	}
 
 	ext_cnt = 0;
-	foreach(lc, stats)
+	foreach(lc, statslist)
 	{
 		StatExtEntry *stat = (StatExtEntry *) lfirst(lc);
 		MVNDistinct *ndistinct = NULL;
 		MVDependencies *dependencies = NULL;
 		MCVList    *mcv = NULL;
+		Datum		exprstats = (Datum) 0;
 		VacAttrStats **stats;
 		ListCell   *lc2;
 		int			stattarget;
+		ExprInfo   *exprs;
+		int			min_attrs;
 
 		/*
 		 * Check if we can build these stats based on the column analyzed. If
 		 * not, report this fact (except in autovacuum) and move on.
 		 */
-		stats = lookup_var_attr_stats(onerel, stat->columns,
+		stats = lookup_var_attr_stats(onerel, stat->columns, stat->exprs,
 									  natts, vacattrstats);
 		if (!stats)
 		{
@@ -150,9 +173,28 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 			continue;
 		}
 
+		/* determine the minimum required number of attributes/expressions */
+		min_attrs = 1;
+		foreach(lc2, stat->types)
+		{
+			char	t = (char) lfirst_int(lc2);
+
+			switch (t)
+			{
+				/* expressions only need a single item */
+				case STATS_EXT_EXPRESSIONS:
+					break;
+
+				/* all other statistics kinds require at least two */
+				default:
+					min_attrs = 2;
+					break;
+			}
+		}
+
 		/* check allowed number of dimensions */
-		Assert(bms_num_members(stat->columns) >= 2 &&
-			   bms_num_members(stat->columns) <= STATS_MAX_DIMENSIONS);
+		Assert(bms_num_members(stat->columns) + list_length(stat->exprs) >= min_attrs &&
+			   bms_num_members(stat->columns) + list_length(stat->exprs) <= STATS_MAX_DIMENSIONS);
 
 		/* compute statistics target for this statistics */
 		stattarget = statext_compute_stattarget(stat->stattarget,
@@ -167,6 +209,9 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 		if (stattarget == 0)
 			continue;
 
+		/* evaluate expressions (if the statistics has any) */
+		exprs = evaluate_expressions(onerel, stat->exprs, numrows, rows);
+
 		/* compute statistic of each requested type */
 		foreach(lc2, stat->types)
 		{
@@ -174,21 +219,43 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 
 			if (t == STATS_EXT_NDISTINCT)
 				ndistinct = statext_ndistinct_build(totalrows, numrows, rows,
-													stat->columns, stats);
+													exprs, stat->columns,
+													stats);
 			else if (t == STATS_EXT_DEPENDENCIES)
 				dependencies = statext_dependencies_build(numrows, rows,
-														  stat->columns, stats);
+														  exprs, stat->columns,
+														  stats);
 			else if (t == STATS_EXT_MCV)
-				mcv = statext_mcv_build(numrows, rows, stat->columns, stats,
-										totalrows, stattarget);
+				mcv = statext_mcv_build(numrows, rows, exprs, stat->columns,
+										stats, totalrows, stattarget);
+			else if (t == STATS_EXT_EXPRESSIONS)
+			{
+				AnlExprData *exprdata;
+				int			nexprs;
+
+				/* should not happen, thanks to checks when defining stats */
+				if (!stat->exprs)
+					elog(ERROR, "requested expression stats, but there are no expressions");
+
+				exprdata = build_expr_data(stat->exprs);
+				nexprs = list_length(stat->exprs);
+
+				compute_expr_stats(onerel, totalrows,
+								   exprdata, nexprs,
+								   rows, numrows);
+
+				exprstats = serialize_expr_stats(exprdata, nexprs);
+			}
 		}
 
 		/* store the statistics in the catalog */
-		statext_store(stat->statOid, ndistinct, dependencies, mcv, stats);
+		statext_store(stat->statOid, ndistinct, dependencies, mcv, exprstats, stats);
 
 		/* for reporting progress */
 		pgstat_progress_update_param(PROGRESS_ANALYZE_EXT_STATS_COMPUTED,
 									 ++ext_cnt);
+
+		pfree(exprs);
 	}
 
 	table_close(pg_stext, RowExclusiveLock);
@@ -241,7 +308,7 @@ ComputeExtStatisticsRows(Relation onerel,
 		 * 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,
+		stats = lookup_var_attr_stats(onerel, stat->columns, stat->exprs,
 									  natts, vacattrstats);
 
 		if (!stats)
@@ -349,6 +416,10 @@ statext_is_kind_built(HeapTuple htup, char type)
 			attnum = Anum_pg_statistic_ext_data_stxdmcv;
 			break;
 
+		case STATS_EXT_EXPRESSIONS:
+			attnum = Anum_pg_statistic_ext_data_stxdexpr;
+			break;
+
 		default:
 			elog(ERROR, "unexpected statistics type requested: %d", type);
 	}
@@ -388,6 +459,7 @@ fetch_statentries_for_relation(Relation pg_statext, Oid relid)
 		ArrayType  *arr;
 		char	   *enabled;
 		Form_pg_statistic_ext staForm;
+		List	   *exprs = NIL;
 
 		entry = palloc0(sizeof(StatExtEntry));
 		staForm = (Form_pg_statistic_ext) GETSTRUCT(htup);
@@ -415,10 +487,39 @@ fetch_statentries_for_relation(Relation pg_statext, Oid relid)
 		{
 			Assert((enabled[i] == STATS_EXT_NDISTINCT) ||
 				   (enabled[i] == STATS_EXT_DEPENDENCIES) ||
-				   (enabled[i] == STATS_EXT_MCV));
+				   (enabled[i] == STATS_EXT_MCV) ||
+				   (enabled[i] == STATS_EXT_EXPRESSIONS));
 			entry->types = lappend_int(entry->types, (int) enabled[i]);
 		}
 
+		/* decode expression (if any) */
+		datum = SysCacheGetAttr(STATEXTOID, htup,
+								Anum_pg_statistic_ext_stxexprs, &isnull);
+
+		if (!isnull)
+		{
+			char *exprsString;
+
+			exprsString = TextDatumGetCString(datum);
+			exprs = (List *) stringToNode(exprsString);
+
+			pfree(exprsString);
+
+			/*
+			 * Run the expressions through eval_const_expressions. This is not just an
+			 * optimization, but is necessary, because the planner will be comparing
+			 * them to similarly-processed qual clauses, and may fail to detect valid
+			 * matches without this.  We must not use canonicalize_qual, however,
+			 * since these aren't qual expressions.
+			 */
+			exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
+
+			/* May as well fix opfuncids too */
+			fix_opfuncids((Node *) exprs);
+		}
+
+		entry->exprs = exprs;
+
 		result = lappend(result, entry);
 	}
 
@@ -427,6 +528,86 @@ fetch_statentries_for_relation(Relation pg_statext, Oid relid)
 	return result;
 }
 
+
+/*
+ * examine_attribute -- pre-analysis of a single column
+ *
+ * Determine whether the column is analyzable; if so, create and initialize
+ * a VacAttrStats struct for it.  If not, return NULL.
+ */
+static VacAttrStats *
+examine_attribute(Node *expr)
+{
+	HeapTuple	typtuple;
+	VacAttrStats *stats;
+	int			i;
+	bool		ok;
+
+	/*
+	 * Create the VacAttrStats struct.  Note that we only have a copy of the
+	 * fixed fields of the pg_attribute tuple.
+	 */
+	stats = (VacAttrStats *) palloc0(sizeof(VacAttrStats));
+
+	/* fake the attribute */
+	stats->attr = (Form_pg_attribute) palloc0(ATTRIBUTE_FIXED_PART_SIZE);
+	stats->attr->attstattarget = -1;
+
+	/*
+	 * When analyzing an expression index, believe the expression tree's type
+	 * not the column datatype --- the latter might be the opckeytype storage
+	 * type of the opclass, which is not interesting for our purposes.  (Note:
+	 * if we did anything with non-expression index columns, we'd need to
+	 * figure out where to get the correct type info from, but for now that's
+	 * not a problem.)	It's not clear whether anyone will care about the
+	 * typmod, but we store that too just in case.
+	 */
+	stats->attrtypid = exprType(expr);
+	stats->attrtypmod = exprTypmod(expr);
+	stats->attrcollid = exprCollation(expr);
+
+	typtuple = SearchSysCacheCopy1(TYPEOID,
+								   ObjectIdGetDatum(stats->attrtypid));
+	if (!HeapTupleIsValid(typtuple))
+		elog(ERROR, "cache lookup failed for type %u", stats->attrtypid);
+	stats->attrtype = (Form_pg_type) GETSTRUCT(typtuple);
+	// stats->anl_context = anl_context;
+	stats->tupattnum = InvalidAttrNumber;
+
+	/*
+	 * The fields describing the stats->stavalues[n] element types default to
+	 * the type of the data being analyzed, but the type-specific typanalyze
+	 * function can change them if it wants to store something else.
+	 */
+	for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
+	{
+		stats->statypid[i] = stats->attrtypid;
+		stats->statyplen[i] = stats->attrtype->typlen;
+		stats->statypbyval[i] = stats->attrtype->typbyval;
+		stats->statypalign[i] = stats->attrtype->typalign;
+	}
+
+	/*
+	 * Call the type-specific typanalyze function.  If none is specified, use
+	 * std_typanalyze().
+	 */
+	if (OidIsValid(stats->attrtype->typanalyze))
+		ok = DatumGetBool(OidFunctionCall1(stats->attrtype->typanalyze,
+										   PointerGetDatum(stats)));
+	else
+		ok = std_typanalyze(stats);
+
+	if (!ok || stats->compute_stats == NULL || stats->minrows <= 0)
+	{
+		heap_freetuple(typtuple);
+		pfree(stats->attr);
+		pfree(stats);
+		return NULL;
+	}
+
+	return stats;
+}
+
 /*
  * Using 'vacatts' of size 'nvacatts' as input data, return a newly built
  * VacAttrStats array which includes only the items corresponding to
@@ -435,15 +616,18 @@ fetch_statentries_for_relation(Relation pg_statext, Oid relid)
  * to the caller that the stats should not be built.
  */
 static VacAttrStats **
-lookup_var_attr_stats(Relation rel, Bitmapset *attrs,
+lookup_var_attr_stats(Relation rel, Bitmapset *attrs, List *exprs,
 					  int nvacatts, VacAttrStats **vacatts)
 {
 	int			i = 0;
 	int			x = -1;
+	int			natts;
 	VacAttrStats **stats;
+	ListCell   *lc;
 
-	stats = (VacAttrStats **)
-		palloc(bms_num_members(attrs) * sizeof(VacAttrStats *));
+	natts = bms_num_members(attrs) + list_length(exprs);
+
+	stats = (VacAttrStats **) palloc(natts * sizeof(VacAttrStats *));
 
 	/* lookup VacAttrStats info for the requested columns (same attnum) */
 	while ((x = bms_next_member(attrs, x)) >= 0)
@@ -480,6 +664,24 @@ lookup_var_attr_stats(Relation rel, Bitmapset *attrs,
 		i++;
 	}
 
+	/* also add info for expressions */
+	foreach (lc, exprs)
+	{
+		Node *expr = (Node *) lfirst(lc);
+
+		stats[i] = examine_attribute(expr);
+
+		/*
+		 * FIXME We need tuple descriptor later, and we just grab it from
+		 * stats[0]->tupDesc (see e.g. statext_mcv_build). But as coded
+		 * examine_attribute does not set that, so just grab it from the
+		 * first vacatts element.
+		 */
+		stats[i]->tupDesc = vacatts[0]->tupDesc;
+
+		i++;
+	}
+
 	return stats;
 }
 
@@ -491,7 +693,7 @@ lookup_var_attr_stats(Relation rel, Bitmapset *attrs,
 static void
 statext_store(Oid statOid,
 			  MVNDistinct *ndistinct, MVDependencies *dependencies,
-			  MCVList *mcv, VacAttrStats **stats)
+			  MCVList *mcv, Datum exprs, VacAttrStats **stats)
 {
 	Relation	pg_stextdata;
 	HeapTuple	stup,
@@ -532,11 +734,17 @@ statext_store(Oid statOid,
 		nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = (data == NULL);
 		values[Anum_pg_statistic_ext_data_stxdmcv - 1] = PointerGetDatum(data);
 	}
+	if (exprs != (Datum) 0)
+	{
+		nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = false;
+		values[Anum_pg_statistic_ext_data_stxdexpr - 1] = exprs;
+	}
 
 	/* always replace the value (either by bytea or NULL) */
 	replaces[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
 	replaces[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
 	replaces[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
+	replaces[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
 
 	/* there should already be a pg_statistic_ext_data tuple */
 	oldtup = SearchSysCache1(STATEXTDATASTXOID, ObjectIdGetDatum(statOid));
@@ -741,8 +949,9 @@ build_attnums_array(Bitmapset *attrs, int *numattrs)
  * can simply pfree the return value to release all of it.
  */
 SortItem *
-build_sorted_items(int numrows, int *nitems, HeapTuple *rows, TupleDesc tdesc,
-				   MultiSortSupport mss, int numattrs, AttrNumber *attnums)
+build_sorted_items(int numrows, int *nitems, HeapTuple *rows, ExprInfo *exprs,
+				   TupleDesc tdesc, MultiSortSupport mss,
+				   int numattrs, AttrNumber *attnums)
 {
 	int			i,
 				j,
@@ -789,8 +998,24 @@ build_sorted_items(int numrows, int *nitems, HeapTuple *rows, TupleDesc tdesc,
 		{
 			Datum		value;
 			bool		isnull;
+			int			attlen;
+
+			if (attnums[j] <= MaxHeapAttributeNumber)
+			{
+				value = heap_getattr(rows[i], attnums[j], tdesc, &isnull);
+				attlen = TupleDescAttr(tdesc, attnums[j] - 1)->attlen;
+			}
+			else
+			{
+				int	idx = EXPRESSION_INDEX(attnums[j]);
+
+				Assert((idx >= 0) && (idx < exprs->nexprs));
+
+				value = exprs->values[idx][i];
+				isnull = exprs->nulls[idx][i];
 
-			value = heap_getattr(rows[i], attnums[j], tdesc, &isnull);
+				attlen = get_typlen(exprs->types[idx]);
+			}
 
 			/*
 			 * If this is a varlena value, check if it's too wide and if yes
@@ -801,8 +1026,7 @@ build_sorted_items(int numrows, int *nitems, HeapTuple *rows, TupleDesc tdesc,
 			 * on the assumption that those are small (below WIDTH_THRESHOLD)
 			 * and will be discarded at the end of analyze.
 			 */
-			if ((!isnull) &&
-				(TupleDescAttr(tdesc, attnums[j] - 1)->attlen == -1))
+			if ((!isnull) && (attlen == -1))
 			{
 				if (toast_raw_datum_size(value) > WIDTH_THRESHOLD)
 				{
@@ -881,7 +1105,8 @@ has_stats_of_kind(List *stats, char requiredkind)
  */
 StatisticExtInfo *
 choose_best_statistics(List *stats, char requiredkind,
-					   Bitmapset **clause_attnums, int nclauses)
+					   Bitmapset **clause_attnums, List **clause_exprs,
+					   int nclauses)
 {
 	ListCell   *lc;
 	StatisticExtInfo *best_match = NULL;
@@ -894,6 +1119,7 @@ choose_best_statistics(List *stats, char requiredkind,
 		StatisticExtInfo *info = (StatisticExtInfo *) lfirst(lc);
 		Bitmapset  *matched = NULL;
 		int			num_matched;
+		int			num_matched_exprs;
 		int			numkeys;
 
 		/* skip statistics that are not of the correct type */
@@ -920,6 +1146,38 @@ choose_best_statistics(List *stats, char requiredkind,
 		num_matched = bms_num_members(matched);
 		bms_free(matched);
 
+		/*
+		 * Collect expressions in remaining (unestimated) expressions, covered
+		 * by an expression in this statistic object.
+		 */
+		num_matched_exprs = 0;
+		for (i = 0; i < nclauses; i++)
+		{
+			ListCell *lc3;
+
+			/* ignore incompatible/estimated expressions */
+			if (!clause_exprs[i])
+				continue;
+
+			/* ignore expressions that are not covered by this object */
+			foreach (lc3, clause_exprs[i])
+			{
+				ListCell   *lc2;
+				Node	   *expr = (Node *) lfirst(lc3);
+
+				foreach(lc2, info->exprs)
+				{
+					Node   *stat_expr = (Node *) lfirst(lc2);
+
+					if (equal(expr, stat_expr))
+					{
+						num_matched_exprs++;
+						break;
+					}
+				}
+			}
+		}
+
 		/*
 		 * save the actual number of keys in the stats so that we can choose
 		 * the narrowest stats with the most matching keys.
@@ -931,11 +1189,12 @@ choose_best_statistics(List *stats, char requiredkind,
 		 * when it matches the same number of attributes but these stats have
 		 * fewer keys than any previous match.
 		 */
-		if (num_matched > best_num_matched ||
-			(num_matched == best_num_matched && numkeys < best_match_keys))
+		if (num_matched + num_matched_exprs > best_num_matched ||
+			((num_matched + num_matched_exprs) == best_num_matched &&
+			 numkeys < best_match_keys))
 		{
 			best_match = info;
-			best_num_matched = num_matched;
+			best_num_matched = num_matched + num_matched_exprs;
 			best_match_keys = numkeys;
 		}
 	}
@@ -994,7 +1253,7 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
 			return false;
 
 		/* Check if the expression has the right shape (one Var, one Const) */
-		if (!examine_clause_args(expr->args, &var, NULL, NULL))
+		if (!examine_opclause_expression(expr, &var, NULL, NULL))
 			return false;
 
 		/*
@@ -1150,6 +1409,187 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
 	return false;
 }
 
+/*
+ * statext_extract_expression_internal
+ *		FIXME
+ *
+ */
+static List *
+statext_extract_expression_internal(PlannerInfo *root, Node *clause, Index relid)
+{
+	/* Look inside any binary-compatible relabeling (as in examine_variable) */
+	if (IsA(clause, RelabelType))
+		clause = (Node *) ((RelabelType *) clause)->arg;
+
+	/* plain Var references (boolean Vars or recursive checks) */
+	if (IsA(clause, Var))
+	{
+		Var		   *var = (Var *) clause;
+
+		/* Ensure var is from the correct relation */
+		if (var->varno != relid)
+			return NIL;
+
+		/* we also better ensure the Var is from the current level */
+		if (var->varlevelsup > 0)
+			return NIL;
+
+		/* Also skip system attributes (we don't allow stats on those). */
+		if (!AttrNumberIsForUserDefinedAttr(var->varattno))
+			return NIL;
+
+		return list_make1(clause);
+	}
+
+	/* (Var op Const) or (Const op Var) */
+	if (is_opclause(clause))
+	{
+		RangeTblEntry *rte = root->simple_rte_array[relid];
+		OpExpr	   *expr = (OpExpr *) clause;
+		Node	   *expr2 = NULL;
+
+		/* Only expressions with two arguments are considered compatible. */
+		if (list_length(expr->args) != 2)
+			return NIL;
+
+		/* Check if the expression has the right shape (one Expr, one Const) */
+		if (!examine_opclause_expression2(expr, &expr2, NULL, NULL))
+			return NIL;
+
+		/*
+		 * If it's not one of the supported operators ("=", "<", ">", etc.),
+		 * just ignore the clause, as it's not compatible with MCV lists.
+		 *
+		 * This uses the function for estimating selectivity, not the operator
+		 * directly (a bit awkward, but well ...).
+		 */
+		switch (get_oprrest(expr->opno))
+		{
+			case F_EQSEL:
+			case F_NEQSEL:
+			case F_SCALARLTSEL:
+			case F_SCALARLESEL:
+			case F_SCALARGTSEL:
+			case F_SCALARGESEL:
+				/* supported, will continue with inspection of the Var */
+				break;
+
+			default:
+				/* other estimators are considered unknown/unsupported */
+				return NIL;
+		}
+
+		/*
+		 * If there are any securityQuals on the RTE from security barrier
+		 * views or RLS policies, then the user may not have access to all the
+		 * table's data, and we must check that the operator is leak-proof.
+		 *
+		 * If the operator is leaky, then we must ignore this clause for the
+		 * purposes of estimating with MCV lists, otherwise the operator might
+		 * reveal values from the MCV list that the user doesn't have
+		 * permission to see.
+		 */
+		if (rte->securityQuals != NIL &&
+			!get_func_leakproof(get_opcode(expr->opno)))
+			return NIL;
+
+		return list_make1(expr2);
+	}
+
+	if (IsA(clause, ScalarArrayOpExpr))
+	{
+		RangeTblEntry *rte = root->simple_rte_array[relid];
+		ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause;
+		Node	   *expr2 = NULL;
+
+		/* Only expressions with two arguments are considered compatible. */
+		if (list_length(expr->args) != 2)
+			return NIL;
+
+		/* Check if the expression has the right shape (one Expr, one Const) */
+		if (!examine_clause_args2(expr->args, &expr2, NULL, NULL))
+			return NIL;
+
+		/*
+		 * If there are any securityQuals on the RTE from security barrier
+		 * views or RLS policies, then the user may not have access to all the
+		 * table's data, and we must check that the operator is leak-proof.
+		 *
+		 * If the operator is leaky, then we must ignore this clause for the
+		 * purposes of estimating with MCV lists, otherwise the operator might
+		 * reveal values from the MCV list that the user doesn't have
+		 * permission to see.
+		 */
+		if (rte->securityQuals != NIL &&
+			!get_func_leakproof(get_opcode(expr->opno)))
+			return NIL;
+
+		return list_make1(expr2);
+	}
+
+	/* AND/OR/NOT clause */
+	if (is_andclause(clause) ||
+		is_orclause(clause) ||
+		is_notclause(clause))
+	{
+		/*
+		 * AND/OR/NOT-clauses are supported if all sub-clauses are supported
+		 *
+		 * Perhaps we could improve this by handling mixed cases, when some of
+		 * the clauses are supported and some are not. Selectivity for the
+		 * supported subclauses would be computed using extended statistics,
+		 * and the remaining clauses would be estimated using the traditional
+		 * algorithm (product of selectivities).
+		 *
+		 * It however seems overly complex, and in a way we already do that
+		 * because if we reject the whole clause as unsupported here, it will
+		 * be eventually passed to clauselist_selectivity() which does exactly
+		 * this (split into supported/unsupported clauses etc).
+		 */
+		BoolExpr   *expr = (BoolExpr *) clause;
+		ListCell   *lc;
+		List	   *exprs = NIL;
+
+		foreach(lc, expr->args)
+		{
+			List *tmp;
+
+			/*
+			 * Had we found incompatible clause in the arguments, treat the
+			 * whole clause as incompatible.
+			 */
+			tmp = statext_extract_expression_internal(root,
+													  (Node *) lfirst(lc),
+													  relid);
+
+			if (!tmp)
+				return NIL;
+
+			exprs = list_concat(exprs, tmp);
+		}
+
+		return exprs;
+	}
+
+	/* Var IS NULL */
+	if (IsA(clause, NullTest))
+	{
+		NullTest   *nt = (NullTest *) clause;
+
+		/*
+		 * Only simple (Var IS NULL) expressions supported for now. Maybe we
+		 * could use examine_variable to fix this?
+		 */
+		if (!IsA(nt->arg, Var))
+			return NIL;
+
+		return statext_extract_expression_internal(root, (Node *) (nt->arg),
+												   relid);
+	}
+
+	return NIL;
+}
+
 /*
  * statext_is_compatible_clause
  *		Determines if the clause is compatible with MCV lists.
@@ -1163,6 +1603,8 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
  *
  * (c) combinations using AND/OR/NOT
  *
+ * (d) ScalarArrayOpExprs of the form (Var op ANY (array)) or (Var op ALL (array))
+ *
  * In the future, the range of supported clauses may be expanded to more
  * complex cases, for example (Var op Var).
  */
@@ -1250,15 +1692,62 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 }
 
 /*
- * statext_mcv_clauselist_selectivity
- *		Estimate clauses using the best multi-column statistics.
+ * statext_extract_expression
+ *		Determines if the clause is compatible with extended statistics.
  *
- * Applies available extended (multi-column) statistics on a table. There may
- * be multiple applicable statistics (with respect to the clauses), in which
- * case we use greedy approach. In each round we select the best statistic on
- * a table (measured by the number of attributes extracted from the clauses
- * and covered by it), and compute the selectivity for the supplied clauses.
- * We repeat this process with the remaining clauses (if any), until none of
+ * Currently, we only support three types of clauses:
+ *
+ * (a) OpExprs of the form (Var op Const), or (Const op Var), where the op
+ * is one of ("=", "<", ">", ">=", "<=")
+ *
+ * (b) (Var IS [NOT] NULL)
+ *
+ * (c) combinations using AND/OR/NOT
+ *
+ * (d) ScalarArrayOpExprs of the form (Var op ANY (array)) or (Var op ALL (array))
+ *
+ * In the future, the range of supported clauses may be expanded to more
+ * complex cases, for example (Var op Var).
+ */
+static List *
+statext_extract_expression(PlannerInfo *root, Node *clause, Index relid)
+{
+	RestrictInfo *rinfo = (RestrictInfo *) clause;
+	List		 *exprs;
+
+	if (!IsA(rinfo, RestrictInfo))
+		return NIL;
+
+	/* Pseudoconstants are not really interesting here. */
+	if (rinfo->pseudoconstant)
+		return NIL;
+
+	/* clauses referencing multiple varnos are incompatible */
+	if (bms_membership(rinfo->clause_relids) != BMS_SINGLETON)
+		return NIL;
+
+	/* Check the clause and determine what attributes it references. */
+	exprs = statext_extract_expression_internal(root, (Node *) rinfo->clause, relid);
+
+	if (!exprs)
+		return NIL;
+
+	/* FIXME do the same ACL check as in statext_is_compatible_clause */
+
+	/* If we reach here, the clause is OK */
+	return exprs;
+}
+
+/*
+ * statext_mcv_clauselist_selectivity
+ *		Estimate clauses using the best multi-column statistics.
+ *
+ * Applies available extended (multi-column) statistics on a table. There may
+ * be multiple applicable statistics (with respect to the clauses), in which
+ * case we use greedy approach. In each round we select the best statistic on
+ * a table (measured by the number of attributes extracted from the clauses
+ * and covered by it), and compute the selectivity for the supplied clauses.
+ * We repeat this process with the remaining clauses (if any), until none of
  * the available statistics can be used.
  *
  * One of the main challenges with using MCV lists is how to extrapolate the
@@ -1290,7 +1779,8 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 								   bool is_or)
 {
 	ListCell   *l;
-	Bitmapset **list_attnums;
+	Bitmapset **list_attnums;	/* attnums extracted from the clause */
+	List	  **list_exprs;		/* expressions matched to any statistic */
 	int			listidx;
 	Selectivity sel = (is_or) ? 0.0 : 1.0;
 
@@ -1301,6 +1791,9 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 	list_attnums = (Bitmapset **) palloc(sizeof(Bitmapset *) *
 										 list_length(clauses));
 
+	/* expressions extracted from complex expressions */
+	list_exprs = (List **) palloc(sizeof(Node *) * list_length(clauses));
+
 	/*
 	 * Pre-process the clauses list to extract the attnums seen in each item.
 	 * We need to determine if there's any clauses which will be useful for
@@ -1318,11 +1811,100 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 		Node	   *clause = (Node *) lfirst(l);
 		Bitmapset  *attnums = NULL;
 
+		/* the clause is considered incompatible by default */
+		list_attnums[listidx] = NULL;
+
+		/* and it's also not covered exactly by the statistic */
+		list_exprs[listidx] = NULL;
+
+		/*
+		 * First see if the clause is simple enough to be covered directly
+		 * by the attributes. If not, see if there's at least one statistic
+		 * object using the expression as-is.
+		 */
 		if (!bms_is_member(listidx, *estimatedclauses) &&
 			statext_is_compatible_clause(root, clause, rel->relid, &attnums))
+		{
+			/* simple expression, covered through attnum(s) */
 			list_attnums[listidx] = attnums;
+		}
 		else
-			list_attnums[listidx] = NULL;
+		{
+			ListCell   *lc;
+			List	 *exprs;
+
+			/*
+			 * XXX This is kinda dubious, because we extract the smallest
+			 * clauses - e.g. from (Var op Const) we extract Var. But maybe
+			 * the statistics covers larger expressions, so maybe this will
+			 * skip that. For example give ((a+b) + (c+d)) it's not clear
+			 * if we should extract the whole clause or some smaller parts.
+			 * OTOH we need (Expr op Const) so maybe we only care about the
+			 * clause as a whole?
+			 */
+			exprs = statext_extract_expression(root, clause, rel->relid);
+
+			/* complex expression, search for statistic covering all parts */
+			foreach(lc, rel->statlist)
+			{
+				ListCell		   *le;
+				StatisticExtInfo   *info = (StatisticExtInfo *) lfirst(lc);
+
+				/*
+				 * Assume all parts are covered by this statistics, we'll
+				 * stop if we found part that is not covered.
+				 */
+				bool covered = true;
+
+				/* have we already matched the expression to a statistic? */
+				Assert(!list_exprs[listidx]);
+
+				/* no expressions in the statistic */
+				if (!info->exprs)
+					continue;
+
+				foreach(le, exprs)
+				{
+					ListCell   *lc2;
+					Node	   *expr = (Node *) lfirst(le);
+					bool		found = false;
+
+					/*
+					 * Walk the expressions, see if all expressions extracted from
+					 * the clause are covered by the extended statistic object.
+					 */
+					foreach (lc2, info->exprs)
+					{
+						Node   *stat_expr = (Node *) lfirst(lc2);
+
+						if (equal(expr, stat_expr))
+						{
+							found = true;
+							break;
+						}
+					}
+
+					/* found expression not covered by the statistics, stop */
+					if (!found)
+					{
+						covered = false;
+						break;
+					}
+				}
+
+				/*
+				 * OK, we found a statistics covering this clause, stop looking
+				 * for another one
+				 */
+				if (covered)
+				{
+					/* XXX should this add the original expression instead? */
+					list_exprs[listidx] = exprs;
+					break;
+				}
+
+			}
+		}
 
 		listidx++;
 	}
@@ -1336,7 +1918,8 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 
 		/* find the best suited statistics object for these attnums */
 		stat = choose_best_statistics(rel->statlist, STATS_EXT_MCV,
-									  list_attnums, list_length(clauses));
+									  list_attnums, list_exprs,
+									  list_length(clauses));
 
 		/*
 		 * if no (additional) matching stats could be found then we've nothing
@@ -1359,11 +1942,13 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 		{
 			/*
 			 * If the clause is compatible with the selected statistics, mark
-			 * it as estimated and add it to the list to estimate.
+			 * it as estimated and add it to the list to estimate. It may be
+			 * either a simple clause, or an expression.
 			 */
 			if (list_attnums[listidx] != NULL &&
 				bms_is_subset(list_attnums[listidx], stat->keys))
 			{
+				/* simple clause (single Var) */
 				if (bms_membership(list_attnums[listidx]) == BMS_SINGLETON)
 					simple_clauses = bms_add_member(simple_clauses,
 													list_length(stat_clauses));
@@ -1374,6 +1959,45 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 				bms_free(list_attnums[listidx]);
 				list_attnums[listidx] = NULL;
 			}
+			else if (list_exprs[listidx] != NIL)
+			{
+				/* are all parts of the expression covered by the statistic? */
+				ListCell   *lc;
+				int			ncovered = 0;
+
+				foreach (lc, list_exprs[listidx])
+				{
+					ListCell   *lc2;
+					Node	   *expr = (Node *) lfirst(lc);
+					bool		found = false;
+
+					foreach (lc2, stat->exprs)
+					{
+						Node   *stat_expr = (Node *) lfirst(lc2);
+
+						if (equal(expr, stat_expr))
+						{
+							found = true;
+							break;
+						}
+					}
+
+					/* count it as covered and continue to the next expression */
+					if (found)
+						ncovered++;
+				}
+
+				/* all parts of thi expression are covered by this statistics */
+				if (ncovered == list_length(list_exprs[listidx]))
+				{
+					stat_clauses = lappend(stat_clauses, (Node *) lfirst(l));
+					*estimatedclauses = bms_add_member(*estimatedclauses, listidx);
+
+					// bms_free(list_attnums[listidx]);
+					list_exprs[listidx] = NULL;
+				}
+
+			}
 
 			listidx++;
 		}
@@ -1621,3 +2245,777 @@ examine_clause_args(List *args, Var **varp, Const **cstp, bool *varonleftp)
 
 	return true;
 }
+
+bool
+examine_clause_args2(List *args, Node **exprp, Const **cstp, bool *expronleftp)
+{
+	Node	   *expr;
+	Const	   *cst;
+	bool		expronleft;
+	Node	   *leftop,
+			   *rightop;
+
+	/* enforced by statext_is_compatible_clause_internal */
+	Assert(list_length(args) == 2);
+
+	leftop = linitial(args);
+	rightop = lsecond(args);
+
+	/* strip RelabelType from either side of the expression */
+	if (IsA(leftop, RelabelType))
+		leftop = (Node *) ((RelabelType *) leftop)->arg;
+
+	if (IsA(rightop, RelabelType))
+		rightop = (Node *) ((RelabelType *) rightop)->arg;
+
+	if (IsA(rightop, Const))
+	{
+		expr = (Node *) leftop;
+		cst = (Const *) rightop;
+		expronleft = true;
+	}
+	else if (IsA(leftop, Const))
+	{
+		expr = (Node *) rightop;
+		cst = (Const *) leftop;
+		expronleft = false;
+	}
+	else
+		return false;
+
+	/* return pointers to the extracted parts if requested */
+	if (exprp)
+		*exprp = expr;
+
+	if (cstp)
+		*cstp = cst;
+
+	if (expronleftp)
+		*expronleftp = expronleft;
+
+	return true;
+}
+
+bool
+examine_opclause_expression(OpExpr *expr, Var **varp, Const **cstp, bool *varonleftp)
+{
+	Var		   *var;
+	Const	   *cst;
+	bool		varonleft;
+	Node	   *leftop,
+			   *rightop;
+
+	/* enforced by statext_is_compatible_clause_internal */
+	Assert(list_length(expr->args) == 2);
+
+	leftop = linitial(expr->args);
+	rightop = lsecond(expr->args);
+
+	/* strip RelabelType from either side of the expression */
+	if (IsA(leftop, RelabelType))
+		leftop = (Node *) ((RelabelType *) leftop)->arg;
+
+	if (IsA(rightop, RelabelType))
+		rightop = (Node *) ((RelabelType *) rightop)->arg;
+
+	if (IsA(leftop, Var) && IsA(rightop, Const))
+	{
+		var = (Var *) leftop;
+		cst = (Const *) rightop;
+		varonleft = true;
+	}
+	else if (IsA(leftop, Const) && IsA(rightop, Var))
+	{
+		var = (Var *) rightop;
+		cst = (Const *) leftop;
+		varonleft = false;
+	}
+	else
+		return false;
+
+	/* return pointers to the extracted parts if requested */
+	if (varp)
+		*varp = var;
+
+	if (cstp)
+		*cstp = cst;
+
+	if (varonleftp)
+		*varonleftp = varonleft;
+
+	return true;
+}
+
+bool
+examine_opclause_expression2(OpExpr *expr, Node **exprp, Const **cstp, bool *expronleftp)
+{
+	Node	   *expr2;
+	Const	   *cst;
+	bool		expronleft;
+	Node	   *leftop,
+			   *rightop;
+
+	/* enforced by statext_is_compatible_clause_internal */
+	Assert(list_length(expr->args) == 2);
+
+	leftop = linitial(expr->args);
+	rightop = lsecond(expr->args);
+
+	/* strip RelabelType from either side of the expression */
+	if (IsA(leftop, RelabelType))
+		leftop = (Node *) ((RelabelType *) leftop)->arg;
+
+	if (IsA(rightop, RelabelType))
+		rightop = (Node *) ((RelabelType *) rightop)->arg;
+
+	if (IsA(rightop, Const))
+	{
+		expr2 = (Node *) leftop;
+		cst = (Const *) rightop;
+		expronleft = true;
+	}
+	else if (IsA(leftop, Const))
+	{
+		expr2 = (Node *) rightop;
+		cst = (Const *) leftop;
+		expronleft = false;
+	}
+	else
+		return false;
+
+	/* return pointers to the extracted parts if requested */
+	if (exprp)
+		*exprp = expr2;
+
+	if (cstp)
+		*cstp = cst;
+
+	if (expronleftp)
+		*expronleftp = expronleft;
+
+	return true;
+}
+
+
+/*
+ * Compute statistics about expressions of a relation.
+ */
+static void
+compute_expr_stats(Relation onerel, double totalrows,
+				   AnlExprData *exprdata, int nexprs,
+				   HeapTuple *rows, int numrows)
+{
+	MemoryContext expr_context,
+				old_context;
+	int			ind,
+				i;
+
+	expr_context = AllocSetContextCreate(CurrentMemoryContext,
+										 "Analyze Expression",
+										 ALLOCSET_DEFAULT_SIZES);
+	old_context = MemoryContextSwitchTo(expr_context);
+
+	for (ind = 0; ind < nexprs; ind++)
+	{
+		AnlExprData *thisdata = &exprdata[ind];
+		Node        *expr = thisdata->expr;
+		TupleTableSlot *slot;
+		EState	   *estate;
+		ExprContext *econtext;
+		Datum	   *exprvals;
+		bool	   *exprnulls;
+		ExprState  *exprstate;
+		int			tcnt;
+
+		/*
+		 * Need an EState for evaluation of expressions.  Create it in
+		 * the per-expression context to be sure it gets cleaned up at
+		 * the bottom of the loop.
+		 */
+		estate = CreateExecutorState();
+		econtext = GetPerTupleExprContext(estate);
+
+		/* Set up expression evaluation state */
+		exprstate = ExecPrepareExpr((Expr *) expr, estate);
+
+		/* Need a slot to hold the current heap tuple, too */
+		slot = MakeSingleTupleTableSlot(RelationGetDescr(onerel),
+										&TTSOpsHeapTuple);
+
+		/* Arrange for econtext's scan tuple to be the tuple under test */
+		econtext->ecxt_scantuple = slot;
+
+		/* Compute and save index expression values */
+		exprvals = (Datum *) palloc(numrows * sizeof(Datum));
+		exprnulls = (bool *) palloc(numrows * sizeof(bool));
+
+		tcnt = 0;
+		for (i = 0; i < numrows; i++)
+		{
+			Datum	datum;
+			bool	isnull;
+
+			/*
+			 * Reset the per-tuple context each time, to reclaim any cruft
+			 * left behind by evaluating the predicate or index expressions.
+			 */
+			ResetExprContext(econtext);
+
+			/* Set up for predicate or expression evaluation */
+			ExecStoreHeapTuple(rows[i], slot, false);
+
+			/*
+			 * FIXME this probably leaks memory. Maybe we should use
+			 * ExecEvalExprSwitchContext but then we need to copy the
+			 * result somewhere else.
+			 */
+			datum = ExecEvalExpr(exprstate,
+								 GetPerTupleExprContext(estate),
+								 &isnull);
+			if (isnull)
+			{
+				exprvals[tcnt] = (Datum) 0;
+				exprnulls[tcnt] = true;
+			}
+			else
+			{
+				exprvals[tcnt] = (Datum) datum;
+				exprnulls[tcnt] = false;
+			}
+
+			tcnt++;
+		}
+
+		/*
+		 * Now we can compute the statistics for the expression columns.
+		 */
+		if (tcnt > 0)
+		{
+			// MemoryContextSwitchTo(col_context);
+			VacAttrStats *stats = thisdata->vacattrstat;
+			AttributeOpts *aopt =
+				get_attribute_options(stats->attr->attrelid,
+									  stats->attr->attnum);
+
+			stats->exprvals = exprvals;
+			stats->exprnulls = exprnulls;
+			stats->rowstride = 1;
+			stats->compute_stats(stats,
+								 expr_fetch_func,
+								 tcnt,
+								 tcnt);
+
+			/*
+			 * If the n_distinct option is specified, it overrides the
+			 * above computation.
+			 */
+			if (aopt != NULL && aopt->n_distinct != 0.0)
+				stats->stadistinct = aopt->n_distinct;
+
+			// MemoryContextResetAndDeleteChildren(col_context);
+		}
+
+		/* And clean up */
+		// MemoryContextSwitchTo(expr_context);
+
+		ExecDropSingleTupleTableSlot(slot);
+		FreeExecutorState(estate);
+		// MemoryContextResetAndDeleteChildren(expr_context);
+	}
+
+	MemoryContextSwitchTo(old_context);
+	MemoryContextDelete(expr_context);
+}
+
+
+/*
+ * Fetch function for analyzing index expressions.
+ *
+ * We have not bothered to construct index tuples, instead the data is
+ * just in Datum arrays.
+ */
+static Datum
+expr_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
+{
+	int			i;
+
+	/* exprvals and exprnulls are already offset for proper column */
+	i = rownum * stats->rowstride;
+	*isNull = stats->exprnulls[i];
+	return stats->exprvals[i];
+}
+
+/*
+ * Build analyze data for a list of expressions. As this is not tied
+ * directly to a relation (table or index), we have to fake some of
+ * the data.
+ */
+static AnlExprData *
+build_expr_data(List *exprs)
+{
+	int				idx;
+	int				nexprs = list_length(exprs);
+	AnlExprData	   *exprdata;
+	ListCell	   *lc;
+
+	exprdata = (AnlExprData *) palloc0(nexprs * sizeof(AnlExprData));
+
+	idx = 0;
+	foreach (lc, exprs)
+	{
+		Node		   *expr = (Node *) lfirst(lc);
+		AnlExprData	   *thisdata = &exprdata[idx];
+
+		thisdata->expr = expr;
+		thisdata->vacattrstat = (VacAttrStats *) palloc(sizeof(VacAttrStats));
+
+		thisdata->vacattrstat = examine_expression(expr);
+		idx++;
+	}
+
+	return exprdata;
+}
+
+/*
+ * examine_expression -- pre-analysis of a single column
+ *
+ * Determine whether the column is analyzable; if so, create and initialize
+ * a VacAttrStats struct for it.  If not, return NULL.
+ */
+static VacAttrStats *
+examine_expression(Node *expr)
+{
+	HeapTuple	typtuple;
+	VacAttrStats *stats;
+	int			i;
+	bool		ok;
+
+	Assert(expr != NULL);
+
+	/*
+	 * Create the VacAttrStats struct.
+	 */
+	stats = (VacAttrStats *) palloc0(sizeof(VacAttrStats));
+
+	/*
+	 * When analyzing an expression, believe the expression tree's type.
+	 */
+	stats->attrtypid = exprType(expr);
+	stats->attrtypmod = exprTypmod(expr);
+
+	/*
+	 * XXX Do we need to do anything special about the collation, similar
+	 * to what examine_attribute does for expression indexes?
+	 */
+	stats->attrcollid = exprCollation(expr);
+
+	/*
+	 * We don't have any pg_attribute for expressions, so let's fake
+	 * something reasonable into attstattarget, which is the only thing
+	 * std_typanalyze needs.
+	 */
+	stats->attr = (Form_pg_attribute) palloc(ATTRIBUTE_FIXED_PART_SIZE);
+
+	/*
+	 * FIXME we should probably get the target from the extended stats
+	 * object, or something like that.
+	 */
+	stats->attr->attstattarget = default_statistics_target;
+
+	/* initialize some basic fields */
+	stats->attr->attrelid = InvalidOid;
+	stats->attr->attnum = InvalidAttrNumber;
+	stats->attr->atttypid = stats->attrtypid;
+
+	typtuple = SearchSysCacheCopy1(TYPEOID,
+								   ObjectIdGetDatum(stats->attrtypid));
+	if (!HeapTupleIsValid(typtuple))
+		elog(ERROR, "cache lookup failed for type %u", stats->attrtypid);
+	stats->attrtype = (Form_pg_type) GETSTRUCT(typtuple);
+	stats->anl_context = CurrentMemoryContext;	/* XXX should be using something else? */
+	stats->tupattnum = InvalidAttrNumber;
+
+	/*
+	 * The fields describing the stats->stavalues[n] element types default to
+	 * the type of the data being analyzed, but the type-specific typanalyze
+	 * function can change them if it wants to store something else.
+	 */
+	for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
+	{
+		stats->statypid[i] = stats->attrtypid;
+		stats->statyplen[i] = stats->attrtype->typlen;
+		stats->statypbyval[i] = stats->attrtype->typbyval;
+		stats->statypalign[i] = stats->attrtype->typalign;
+	}
+
+	/*
+	 * Call the type-specific typanalyze function.  If none is specified, use
+	 * std_typanalyze().
+	 */
+	if (OidIsValid(stats->attrtype->typanalyze))
+		ok = DatumGetBool(OidFunctionCall1(stats->attrtype->typanalyze,
+										   PointerGetDatum(stats)));
+	else
+		ok = std_typanalyze(stats);
+
+	if (!ok || stats->compute_stats == NULL || stats->minrows <= 0)
+	{
+		heap_freetuple(typtuple);
+		pfree(stats);
+		return NULL;
+	}
+
+	return stats;
+}
+
+/* form an array of pg_statistic rows (per update_attstats) */
+static Datum
+serialize_expr_stats(AnlExprData *exprdata, int nexprs)
+{
+	int			exprno;
+	Oid			typOid;
+	Relation	sd;
+
+	ArrayBuildState *astate = NULL;
+
+	sd = table_open(StatisticRelationId, RowExclusiveLock);
+
+	/* lookup OID of composite type for pg_statistic */
+	typOid = get_rel_type_id(StatisticRelationId);
+	if (!OidIsValid(typOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("relation \"pg_statistic\" does not have a composite type")));
+
+	for (exprno = 0; exprno < nexprs; exprno++)
+	{
+		int				i, k;
+		VacAttrStats   *stats = exprdata[exprno].vacattrstat;
+
+		Datum		values[Natts_pg_statistic];
+		bool		nulls[Natts_pg_statistic];
+		HeapTuple	stup;
+
+		if (!stats->stats_valid)
+		{
+			astate = accumArrayResult(astate,
+									  (Datum) 0,
+									  true,
+									  typOid,
+									  CurrentMemoryContext);
+			continue;
+		}
+
+		/*
+		 * Construct a new pg_statistic tuple
+		 */
+		for (i = 0; i < Natts_pg_statistic; ++i)
+		{
+			nulls[i] = false;
+		}
+
+		values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(InvalidOid);
+		values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(InvalidAttrNumber);
+		values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(false);
+		values[Anum_pg_statistic_stanullfrac - 1] = Float4GetDatum(stats->stanullfrac);
+		values[Anum_pg_statistic_stawidth - 1] = Int32GetDatum(stats->stawidth);
+		values[Anum_pg_statistic_stadistinct - 1] = Float4GetDatum(stats->stadistinct);
+		i = Anum_pg_statistic_stakind1 - 1;
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			values[i++] = Int16GetDatum(stats->stakind[k]); /* stakindN */
+		}
+		i = Anum_pg_statistic_staop1 - 1;
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			values[i++] = ObjectIdGetDatum(stats->staop[k]);	/* staopN */
+		}
+		i = Anum_pg_statistic_stacoll1 - 1;
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			values[i++] = ObjectIdGetDatum(stats->stacoll[k]);	/* stacollN */
+		}
+		i = Anum_pg_statistic_stanumbers1 - 1;
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			int			nnum = stats->numnumbers[k];
+
+			if (nnum > 0)
+			{
+				int			n;
+				Datum	   *numdatums = (Datum *) palloc(nnum * sizeof(Datum));
+				ArrayType  *arry;
+
+				for (n = 0; n < nnum; n++)
+					numdatums[n] = Float4GetDatum(stats->stanumbers[k][n]);
+				/* XXX knows more than it should about type float4: */
+				arry = construct_array(numdatums, nnum,
+									   FLOAT4OID,
+									   sizeof(float4), true, TYPALIGN_INT);
+				values[i++] = PointerGetDatum(arry);	/* stanumbersN */
+			}
+			else
+			{
+				nulls[i] = true;
+				values[i++] = (Datum) 0;
+			}
+		}
+		i = Anum_pg_statistic_stavalues1 - 1;
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			if (stats->numvalues[k] > 0)
+			{
+				ArrayType  *arry;
+
+				arry = construct_array(stats->stavalues[k],
+									   stats->numvalues[k],
+									   stats->statypid[k],
+									   stats->statyplen[k],
+									   stats->statypbyval[k],
+									   stats->statypalign[k]);
+				values[i++] = PointerGetDatum(arry);	/* stavaluesN */
+			}
+			else
+			{
+				nulls[i] = true;
+				values[i++] = (Datum) 0;
+			}
+		}
+
+		stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+
+		astate = accumArrayResult(astate,
+								  heap_copy_tuple_as_datum(stup, RelationGetDescr(sd)),
+								  false,
+								  typOid,
+								  CurrentMemoryContext);
+	}
+
+	table_close(sd, RowExclusiveLock);
+
+	return makeArrayResult(astate, CurrentMemoryContext);
+}
+
+
+/*
+ * Loads pg_statistic record from expression statistics for expression
+ * identified by the supplied index.
+ */
+HeapTuple
+statext_expressions_load(Oid stxoid, int idx)
+{
+	bool		isnull;
+	Datum		value;
+	HeapTuple	htup;
+	ExpandedArrayHeader *eah;
+	HeapTupleHeader td;
+	HeapTupleData tmptup;
+	HeapTuple	tup;
+
+	htup = SearchSysCache1(STATEXTDATASTXOID, ObjectIdGetDatum(stxoid));
+	if (!HeapTupleIsValid(htup))
+		elog(ERROR, "cache lookup failed for statistics object %u", stxoid);
+
+	value = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+							Anum_pg_statistic_ext_data_stxdexpr, &isnull);
+	if (isnull)
+		elog(ERROR,
+			 "requested statistic kind \"%c\" is not yet built for statistics object %u",
+			 STATS_EXT_DEPENDENCIES, stxoid);
+
+	eah = DatumGetExpandedArray(value);
+
+	deconstruct_expanded_array(eah);
+
+	td = DatumGetHeapTupleHeader(eah->dvalues[idx]);
+
+	/* Build a temporary HeapTuple control structure */
+	tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+	tmptup.t_data = td;
+
+	tup = heap_copytuple(&tmptup);
+
+	ReleaseSysCache(htup);
+
+	return tup;
+}
+
+/*
+ * Evaluate the expressions, so that we can use the results to build
+ * all the requested statistics types. This matters especially for
+ * expensive expressions, of course.
+ */
+static ExprInfo *
+evaluate_expressions(Relation rel, List *exprs, int numrows, HeapTuple *rows)
+{
+	/* evaluated expressions */
+	ExprInfo   *result;
+	char	   *ptr;
+	Size		len;
+
+	int			i;
+	int			idx;
+	TupleTableSlot *slot;
+	EState	   *estate;
+	ExprContext *econtext;
+	List	   *exprstates = NIL;
+	int			nexprs = list_length(exprs);
+	ListCell   *lc;
+
+	/* allocate everything as a single chunk, so we can free it easily */
+	len = MAXALIGN(sizeof(ExprInfo));
+	len += MAXALIGN(sizeof(Oid) * nexprs);	/* types */
+	len += MAXALIGN(sizeof(Oid) * nexprs);	/* collations */
+
+	/* values */
+	len += MAXALIGN(sizeof(Datum *) * nexprs);
+	len += nexprs * MAXALIGN(sizeof(Datum) * numrows);
+
+	/* nulls */
+	len += MAXALIGN(sizeof(bool *) * nexprs);
+	len += nexprs * MAXALIGN(sizeof(bool) * numrows);
+
+	ptr = palloc(len);
+
+	/* set the pointers */
+	result = (ExprInfo *) ptr;
+	ptr += sizeof(ExprInfo);
+
+	/* types */
+	result->types = (Oid *) ptr;
+	ptr += MAXALIGN(sizeof(Oid) * nexprs);
+
+	/* collations */
+	result->collations = (Oid *) ptr;
+	ptr += MAXALIGN(sizeof(Oid) * nexprs);
+
+	/* values */
+	result->values = (Datum **) ptr;
+	ptr += MAXALIGN(sizeof(Datum *) * nexprs);
+
+	/* nulls */
+	result->nulls = (bool **) ptr;
+	ptr += MAXALIGN(sizeof(bool *) * nexprs);
+
+	for (i = 0; i < nexprs; i++)
+	{
+		result->values[i] = (Datum *) ptr;
+		ptr += MAXALIGN(sizeof(Datum) * numrows);
+
+		result->nulls[i] = (bool *) ptr;
+		ptr += MAXALIGN(sizeof(bool) * numrows);
+	}
+
+	Assert((ptr - (char *) result) == len);
+
+	result->nexprs = list_length(exprs);
+
+	idx = 0;
+	foreach (lc, exprs)
+	{
+		Node *expr = (Node *) lfirst(lc);
+
+		result->types[idx] = exprType(expr);
+		result->collations[idx] = exprCollation(expr);
+
+		idx++;
+	}
+
+	/*
+	 * Need an EState for evaluation of index expressions and
+	 * partial-index predicates.  Create it in the per-index context to be
+	 * sure it gets cleaned up at the bottom of the loop.
+	 */
+	estate = CreateExecutorState();
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Need a slot to hold the current heap tuple, too */
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(rel),
+									&TTSOpsHeapTuple);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/* Set up expression evaluation state */
+	exprstates = ExecPrepareExprList(exprs, estate);
+
+	for (i = 0; i < numrows; i++)
+	{
+		/*
+		 * Reset the per-tuple context each time, to reclaim any cruft
+		 * left behind by evaluating the predicate or index expressions.
+		 */
+		ResetExprContext(econtext);
+
+		/* Set up for predicate or expression evaluation */
+		ExecStoreHeapTuple(rows[i], slot, false);
+
+		idx = 0;
+		foreach (lc, exprstates)
+		{
+			Datum	datum;
+			bool	isnull;
+			ExprState *exprstate = (ExprState *) lfirst(lc);
+
+			/*
+			 * FIXME this probably leaks memory. Maybe we should use
+			 * ExecEvalExprSwitchContext but then we need to copy the
+			 * result somewhere else.
+			 */
+			datum = ExecEvalExpr(exprstate,
+								 GetPerTupleExprContext(estate),
+								 &isnull);
+			if (isnull)
+			{
+				result->values[idx][i] = (Datum) 0;
+				result->nulls[idx][i] = true;
+			}
+			else
+			{
+				result->values[idx][i] = (Datum) datum;
+				result->nulls[idx][i] = false;
+			}
+
+			idx++;
+		}
+	}
+
+	ExecDropSingleTupleTableSlot(slot);
+	FreeExecutorState(estate);
+
+	return result;
+}
+
+/*
+ * add_expressions_to_attributes
+ *		add expressions as attributes with high attnums
+ *
+ * Treat the expressions as attributes with attnums above the regular
+ * attnum range. This will allow us to handle everything in the same
+ * way, and identify expressions in the dependencies.
+ *
+ * XXX This always creates a copy of the bitmap. We might optimize this
+ * by only creating the copy with (nexprs > 0) but then we'd have to track
+ * this in order to free it (if we want to). Does not seem worth it.
+ */
+Bitmapset *
+add_expressions_to_attributes(Bitmapset *attrs, int nexprs)
+{
+	int			i;
+
+	/*
+	 * Copy the bitmapset and add fake attnums representing expressions,
+	 * starting above MaxHeapAttributeNumber.
+	 */
+	attrs = bms_copy(attrs);
+
+	/* start with (MaxHeapAttributeNumber + 1) */
+	for (i = 0; i < nexprs; i++)
+	{
+		Assert(EXPRESSION_ATTNUM(i) > MaxHeapAttributeNumber);
+
+		attrs = bms_add_member(attrs, EXPRESSION_ATTNUM(i));
+	}
+
+	return attrs;
+}
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index abbc1f1ba8..d7075b3d42 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -74,7 +74,8 @@
 	 ((ndims) * sizeof(DimensionInfo)) + \
 	 ((nitems) * ITEM_SIZE(ndims)))
 
-static MultiSortSupport build_mss(VacAttrStats **stats, int numattrs);
+static MultiSortSupport build_mss(VacAttrStats **stats, int numattrs,
+								  ExprInfo *exprs);
 
 static SortItem *build_distinct_groups(int numrows, SortItem *items,
 									   MultiSortSupport mss, int *ndistinct);
@@ -181,8 +182,9 @@ get_mincount_for_mcv_list(int samplerows, double totalrows)
  *
  */
 MCVList *
-statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
-				  VacAttrStats **stats, double totalrows, int stattarget)
+statext_mcv_build(int numrows, HeapTuple *rows, ExprInfo *exprs,
+				  Bitmapset *attrs, VacAttrStats **stats,
+				  double totalrows, int stattarget)
 {
 	int			i,
 				numattrs,
@@ -195,14 +197,23 @@ statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
 	MCVList    *mcvlist = NULL;
 	MultiSortSupport mss;
 
-	attnums = build_attnums_array(attrs, &numattrs);
-
 	/* comparator for all the columns */
-	mss = build_mss(stats, numattrs);
+	mss = build_mss(stats, bms_num_members(attrs), exprs);
+
+	/*
+	 * treat expressions as special attributes with high attnums
+	 *
+	 * XXX We do this after build_mss, because that expects the bitmapset
+	 * to only contain simple attributes (with a matching VacAttrStats)
+	 */
+	attrs = add_expressions_to_attributes(attrs, exprs->nexprs);
+
+	/* now build the array, with the special expression attnums */
+	attnums = build_attnums_array(attrs, &numattrs);
 
 	/* sort the rows */
-	items = build_sorted_items(numrows, &nitems, rows, stats[0]->tupDesc,
-							   mss, numattrs, attnums);
+	items = build_sorted_items(numrows, &nitems, rows, exprs,
+							   stats[0]->tupDesc, mss, numattrs, attnums);
 
 	if (!items)
 		return NULL;
@@ -338,6 +349,7 @@ statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
 
 	pfree(items);
 	pfree(groups);
+	pfree(attrs);
 
 	return mcvlist;
 }
@@ -347,12 +359,12 @@ statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
  *	build MultiSortSupport for the attributes passed in attrs
  */
 static MultiSortSupport
-build_mss(VacAttrStats **stats, int numattrs)
+build_mss(VacAttrStats **stats, int numattrs, ExprInfo *exprs)
 {
 	int			i;
 
 	/* Sort by multiple columns (using array of SortSupport) */
-	MultiSortSupport mss = multi_sort_init(numattrs);
+	MultiSortSupport mss = multi_sort_init(numattrs + exprs->nexprs);
 
 	/* prepare the sort functions for all the attributes */
 	for (i = 0; i < numattrs; i++)
@@ -368,6 +380,20 @@ build_mss(VacAttrStats **stats, int numattrs)
 		multi_sort_add_dimension(mss, i, type->lt_opr, colstat->attrcollid);
 	}
 
+	/* prepare the sort functions for all the expressions */
+	for (i = 0; i < exprs->nexprs; i++)
+	{
+		TypeCacheEntry *type;
+
+		type = lookup_type_cache(exprs->types[i], TYPECACHE_LT_OPR);
+		if (type->lt_opr == InvalidOid) /* shouldn't happen */
+			elog(ERROR, "cache lookup failed for ordering operator for type %u",
+				 exprs->types[i]);
+
+		multi_sort_add_dimension(mss, numattrs + i, type->lt_opr,
+								 exprs->collations[i]);
+	}
+
 	return mss;
 }
 
@@ -1541,10 +1567,14 @@ pg_mcv_list_send(PG_FUNCTION_ARGS)
  * the size to ~1/8. It would also allow us to combine bitmaps simply using
  * & and |, which should be faster than min/max. The bitmaps are fairly
  * small, though (thanks to the cap on the MCV list size).
+ *
+ * XXX There's a lot of code duplication between branches for simple columns
+ * and complex expressions. We should refactor it somehow.
  */
 static bool *
 mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
-					 Bitmapset *keys, MCVList *mcvlist, bool is_or)
+					 Bitmapset *keys, List *exprs,
+					 MCVList *mcvlist, bool is_or)
 {
 	int			i;
 	ListCell   *l;
@@ -1584,8 +1614,10 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
 
 			/* valid only after examine_clause_args returns true */
 			Var		   *var;
+			Node	   *clause_expr;
 			Const	   *cst;
 			bool		varonleft;
+			bool		expronleft;
 
 			fmgr_info(get_opcode(expr->opno), &opproc);
 
@@ -1654,6 +1686,89 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
 					matches[i] = RESULT_MERGE(matches[i], is_or, match);
 				}
 			}
+			/* extract the expr and const from the expression */
+			else if (examine_clause_args2(expr->args, &clause_expr, &cst, &expronleft))
+			{
+				ListCell   *lc;
+				int			idx;
+				Oid			collid = exprCollation(clause_expr);
+
+				/* match the attribute to a dimension of the statistic */
+				idx = bms_num_members(keys);
+
+				foreach(lc, exprs)
+				{
+					Node *stat_expr = (Node *) lfirst(lc);
+
+					if (equal(clause_expr, stat_expr))
+						break;
+
+					idx++;
+				}
+
+				/* index should be valid */
+				Assert((idx >= 0) &&
+					   (idx < bms_num_members(keys) + list_length(exprs)));
+
+				/*
+				 * Walk through the MCV items and evaluate the current clause.
+				 * We can skip items that were already ruled out, and
+				 * terminate if there are no remaining MCV items that might
+				 * possibly match.
+				 */
+				for (i = 0; i < mcvlist->nitems; i++)
+				{
+					bool		match = true;
+					MCVItem    *item = &mcvlist->items[i];
+
+					/*
+					 * When the MCV item or the Const value is NULL we can
+					 * treat this as a mismatch. We must not call the operator
+					 * because of strictness.
+					 */
+					if (item->isnull[idx] || cst->constisnull)
+					{
+						matches[i] = RESULT_MERGE(matches[i], is_or, false);
+						continue;
+					}
+
+					/*
+					 * Skip MCV items that can't change result in the bitmap.
+					 * Once the value gets false for AND-lists, or true for
+					 * OR-lists, we don't need to look at more clauses.
+					 */
+					if (RESULT_IS_FINAL(matches[i], is_or))
+						continue;
+
+					/*
+					 * First check whether the constant is below the lower
+					 * boundary (in that case we can skip the bucket, because
+					 * there's no overlap).
+					 *
+					 * We don't store collations used to build the statistics,
+					 * but we can use the collation for the attribute itself,
+					 * as stored in varcollid. We do reset the statistics
+					 * after a type change (including collation change), so
+					 * this is OK. We may need to relax this after allowing
+					 * extended statistics on expressions.
+					 */
+					if (expronleft)
+						match = DatumGetBool(FunctionCall2Coll(&opproc,
+															   collid,
+															   item->values[idx],
+															   cst->constvalue));
+					else
+						match = DatumGetBool(FunctionCall2Coll(&opproc,
+															   collid,
+															   cst->constvalue,
+															   item->values[idx]));
+
+					/* update the match bitmap with the result */
+					matches[i] = RESULT_MERGE(matches[i], is_or, match);
+				}
+			}
+			else
+				elog(ERROR, "incompatible clause");
 		}
 		else if (IsA(clause, ScalarArrayOpExpr))
 		{
@@ -1662,8 +1777,10 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
 
 			/* valid only after examine_clause_args returns true */
 			Var		   *var;
+			Node	   *clause_expr;
 			Const	   *cst;
 			bool		varonleft;
+			bool		expronleft;
 
 			fmgr_info(get_opcode(expr->opno), &opproc);
 
@@ -1761,14 +1878,155 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
 					matches[i] = RESULT_MERGE(matches[i], is_or, match);
 				}
 			}
+			/* extract the expr and const from the expression */
+			else if (examine_clause_args2(expr->args, &clause_expr, &cst, &expronleft))
+			{
+				ListCell   *lc;
+				int			idx;
+
+				ArrayType  *arrayval;
+				int16		elmlen;
+				bool		elmbyval;
+				char		elmalign;
+				int			num_elems;
+				Datum	   *elem_values;
+				bool	   *elem_nulls;
+				Oid			collid = exprCollation(clause_expr);
+
+				/* ScalarArrayOpExpr has the Var always on the left */
+				Assert(expronleft);
+
+				if (!cst->constisnull)
+				{
+					arrayval = DatumGetArrayTypeP(cst->constvalue);
+					get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+										 &elmlen, &elmbyval, &elmalign);
+					deconstruct_array(arrayval,
+									  ARR_ELEMTYPE(arrayval),
+									  elmlen, elmbyval, elmalign,
+									  &elem_values, &elem_nulls, &num_elems);
+				}
+
+				/* match the attribute to a dimension of the statistic */
+				idx = bms_num_members(keys);
+
+				foreach(lc, exprs)
+				{
+					Node *stat_expr = (Node *) lfirst(lc);
+
+					if (equal(clause_expr, stat_expr))
+						break;
+
+					idx++;
+				}
+
+				/* index should be valid */
+				Assert((idx >= 0) &&
+					   (idx < bms_num_members(keys) + list_length(exprs)));
+
+				/*
+				 * Walk through the MCV items and evaluate the current clause.
+				 * We can skip items that were already ruled out, and
+				 * terminate if there are no remaining MCV items that might
+				 * possibly match.
+				 */
+				for (i = 0; i < mcvlist->nitems; i++)
+				{
+					int			j;
+					bool		match = (expr->useOr ? false : true);
+					MCVItem    *item = &mcvlist->items[i];
+
+					/*
+					 * When the MCV item or the Const value is NULL we can
+					 * treat this as a mismatch. We must not call the operator
+					 * because of strictness.
+					 */
+					if (item->isnull[idx] || cst->constisnull)
+					{
+						matches[i] = RESULT_MERGE(matches[i], is_or, false);
+						continue;
+					}
+
+					/*
+					 * Skip MCV items that can't change result in the bitmap.
+					 * Once the value gets false for AND-lists, or true for
+					 * OR-lists, we don't need to look at more clauses.
+					 */
+					if (RESULT_IS_FINAL(matches[i], is_or))
+						continue;
+
+					for (j = 0; j < num_elems; j++)
+					{
+						Datum		elem_value = elem_values[j];
+						bool		elem_isnull = elem_nulls[j];
+						bool		elem_match;
+
+						/* NULL values always evaluate as not matching. */
+						if (elem_isnull)
+						{
+							match = RESULT_MERGE(match, expr->useOr, false);
+							continue;
+						}
+
+						/*
+						 * Stop evaluating the array elements once we reach
+						 * match value that can't change - ALL() is the same
+						 * as AND-list, ANY() is the same as OR-list.
+						 */
+						if (RESULT_IS_FINAL(match, expr->useOr))
+							break;
+
+						elem_match = DatumGetBool(FunctionCall2Coll(&opproc,
+																	collid,
+																	item->values[idx],
+																	elem_value));
+
+						match = RESULT_MERGE(match, expr->useOr, elem_match);
+					}
+
+					/* update the match bitmap with the result */
+					matches[i] = RESULT_MERGE(matches[i], is_or, match);
+				}
+			}
+			else
+				elog(ERROR, "incompatible clause");
 		}
 		else if (IsA(clause, NullTest))
 		{
 			NullTest   *expr = (NullTest *) clause;
-			Var		   *var = (Var *) (expr->arg);
+			Node	   *clause_expr = (Node *) (expr->arg);
 
 			/* match the attribute to a dimension of the statistic */
-			int			idx = bms_member_index(keys, var->varattno);
+			int			idx = -1;
+
+			if (IsA(clause_expr, Var))
+			{
+				/* simple Var, so just lookup using varattno */
+				Var *var = (Var *) clause_expr;
+
+				idx = bms_member_index(keys, var->varattno);
+			}
+			else
+			{
+				ListCell *lc;
+
+				/* expressions are after the simple columns */
+				idx = bms_num_members(keys);
+
+				/* expression - lookup in stats expressions */
+				foreach(lc, exprs)
+				{
+					Node *stat_expr = (Node *) lfirst(lc);
+
+					if (equal(clause_expr, stat_expr))
+						break;
+
+					idx++;
+				}
+			}
+
+			/* index should be valid */
+			Assert((idx >= 0) && (idx < bms_num_members(keys) + list_length(exprs)));
 
 			/*
 			 * Walk through the MCV items and evaluate the current clause. We
@@ -1811,7 +2069,7 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
 			Assert(list_length(bool_clauses) >= 2);
 
 			/* build the match bitmap for the OR-clauses */
-			bool_matches = mcv_get_match_bitmap(root, bool_clauses, keys,
+			bool_matches = mcv_get_match_bitmap(root, bool_clauses, keys, exprs,
 												mcvlist, is_orclause(clause));
 
 			/*
@@ -1839,7 +2097,7 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
 			Assert(list_length(not_args) == 1);
 
 			/* build the match bitmap for the NOT-clause */
-			not_matches = mcv_get_match_bitmap(root, not_args, keys,
+			not_matches = mcv_get_match_bitmap(root, not_args, keys, exprs,
 											   mcvlist, false);
 
 			/*
@@ -1982,7 +2240,8 @@ mcv_clauselist_selectivity(PlannerInfo *root, StatisticExtInfo *stat,
 	mcv = statext_mcv_load(stat->statOid);
 
 	/* build a match bitmap for the clauses */
-	matches = mcv_get_match_bitmap(root, clauses, stat->keys, mcv, false);
+	matches = mcv_get_match_bitmap(root, clauses, stat->keys, stat->exprs,
+								   mcv, false);
 
 	/* sum frequencies for all the matching MCV items */
 	*basesel = 0.0;
@@ -2056,7 +2315,7 @@ mcv_clause_selectivity_or(PlannerInfo *root, StatisticExtInfo *stat,
 
 	/* build the match bitmap for the new clause */
 	new_matches = mcv_get_match_bitmap(root, list_make1(clause), stat->keys,
-									   mcv, false);
+									   stat->exprs, mcv, false);
 
 	/*
 	 * Sum the frequencies for all the MCV items matching this clause and also
diff --git a/src/backend/statistics/mvdistinct.c b/src/backend/statistics/mvdistinct.c
index 9ef21debb6..c4254d9ad5 100644
--- a/src/backend/statistics/mvdistinct.c
+++ b/src/backend/statistics/mvdistinct.c
@@ -37,7 +37,8 @@
 #include "utils/typcache.h"
 
 static double ndistinct_for_combination(double totalrows, int numrows,
-										HeapTuple *rows, VacAttrStats **stats,
+										HeapTuple *rows, ExprInfo *exprs,
+										int nattrs, VacAttrStats **stats,
 										int k, int *combination);
 static double estimate_ndistinct(double totalrows, int numrows, int d, int f1);
 static int	n_choose_k(int n, int k);
@@ -81,16 +82,21 @@ static void generate_combinations(CombinationGenerator *state);
  *
  * This computes the ndistinct estimate using the same estimator used
  * in analyze.c and then computes the coefficient.
+ *
+ * To handle expressions easily, we treat them as special attributes with
+ * attnums above MaxHeapAttributeNumber, and we assume the expressions are
+ * placed after all simple attributes.
  */
 MVNDistinct *
 statext_ndistinct_build(double totalrows, int numrows, HeapTuple *rows,
-						Bitmapset *attrs, VacAttrStats **stats)
+						ExprInfo *exprs, Bitmapset *attrs,
+						VacAttrStats **stats)
 {
 	MVNDistinct *result;
 	int			k;
 	int			itemcnt;
 	int			numattrs = bms_num_members(attrs);
-	int			numcombs = num_combinations(numattrs);
+	int			numcombs = num_combinations(numattrs + exprs->nexprs);
 
 	result = palloc(offsetof(MVNDistinct, items) +
 					numcombs * sizeof(MVNDistinctItem));
@@ -98,14 +104,20 @@ statext_ndistinct_build(double totalrows, int numrows, HeapTuple *rows,
 	result->type = STATS_NDISTINCT_TYPE_BASIC;
 	result->nitems = numcombs;
 
+	/* treat expressions as special attributes with high attnums */
+	attrs = add_expressions_to_attributes(attrs, exprs->nexprs);
+
+	/* make sure there were no clashes */
+	Assert(bms_num_members(attrs) == numattrs + exprs->nexprs);
+
 	itemcnt = 0;
-	for (k = 2; k <= numattrs; k++)
+	for (k = 2; k <= bms_num_members(attrs); k++)
 	{
 		int		   *combination;
 		CombinationGenerator *generator;
 
 		/* generate combinations of K out of N elements */
-		generator = generator_init(numattrs, k);
+		generator = generator_init(bms_num_members(attrs), k);
 
 		while ((combination = generator_next(generator)))
 		{
@@ -114,10 +126,32 @@ statext_ndistinct_build(double totalrows, int numrows, HeapTuple *rows,
 
 			item->attrs = NULL;
 			for (j = 0; j < k; j++)
-				item->attrs = bms_add_member(item->attrs,
-											 stats[combination[j]]->attr->attnum);
+			{
+				AttrNumber attnum = InvalidAttrNumber;
+
+				/*
+				 * The simple attributes are before expressions, so have
+				 * indexes below numattrs.
+				 * */
+				if (combination[j] < numattrs)
+					attnum = stats[combination[j]]->attr->attnum;
+				else
+				{
+					/* make sure the expression index is valid */
+					Assert((combination[j] - numattrs) >= 0);
+					Assert((combination[j] - numattrs) < exprs->nexprs);
+
+					attnum = EXPRESSION_ATTNUM(combination[j] - numattrs);
+				}
+
+				Assert(attnum != InvalidAttrNumber);
+
+				item->attrs = bms_add_member(item->attrs, attnum);
+			}
+
 			item->ndistinct =
 				ndistinct_for_combination(totalrows, numrows, rows,
+										  exprs, numattrs,
 										  stats, k, combination);
 
 			itemcnt++;
@@ -428,6 +462,7 @@ pg_ndistinct_send(PG_FUNCTION_ARGS)
  */
 static double
 ndistinct_for_combination(double totalrows, int numrows, HeapTuple *rows,
+						  ExprInfo *exprs, int nattrs,
 						  VacAttrStats **stats, int k, int *combination)
 {
 	int			i,
@@ -467,25 +502,57 @@ ndistinct_for_combination(double totalrows, int numrows, HeapTuple *rows,
 	 */
 	for (i = 0; i < k; i++)
 	{
-		VacAttrStats *colstat = stats[combination[i]];
+		Oid				typid;
 		TypeCacheEntry *type;
+		AttrNumber		attnum = InvalidAttrNumber;
+		TupleDesc		tdesc = NULL;
+		Oid				collid = InvalidOid;
+
+		if (combination[i] < nattrs)
+		{
+			VacAttrStats *colstat = stats[combination[i]];
+			typid = colstat->attrtypid;
+			attnum = colstat->attr->attnum;
+			collid = colstat->attrcollid;
+			tdesc = colstat->tupDesc;
+		}
+		else
+		{
+			typid = exprs->types[combination[i] - nattrs];
+			collid = exprs->collations[combination[i] - nattrs];
+		}
 
-		type = lookup_type_cache(colstat->attrtypid, TYPECACHE_LT_OPR);
+		type = lookup_type_cache(typid, TYPECACHE_LT_OPR);
 		if (type->lt_opr == InvalidOid) /* shouldn't happen */
 			elog(ERROR, "cache lookup failed for ordering operator for type %u",
-				 colstat->attrtypid);
+				 typid);
 
 		/* prepare the sort function for this dimension */
-		multi_sort_add_dimension(mss, i, type->lt_opr, colstat->attrcollid);
+		multi_sort_add_dimension(mss, i, type->lt_opr, collid);
 
 		/* accumulate all the data for this dimension into the arrays */
 		for (j = 0; j < numrows; j++)
 		{
-			items[j].values[i] =
-				heap_getattr(rows[j],
-							 colstat->attr->attnum,
-							 colstat->tupDesc,
-							 &items[j].isnull[i]);
+			/*
+			 * The first nattrs indexes identify simple attributes, higher
+			 * indexes are expressions.
+			 */
+			if (combination[i] < nattrs)
+				items[j].values[i] =
+					heap_getattr(rows[j],
+								 attnum,
+								 tdesc,
+								 &items[j].isnull[i]);
+			else
+			{
+				int idx = (combination[i] - nattrs);
+
+				/* make sure the expression index is valid */
+				Assert((idx >= 0) && (idx < exprs->nexprs));
+
+				items[j].values[i] = exprs->values[idx][j];
+				items[j].isnull[i] = exprs->nulls[idx][j];
+			}
 		}
 	}
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 53a511f1da..053e4d1d91 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1834,7 +1834,28 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				address = CreateStatistics((CreateStatsStmt *) parsetree);
+				{
+					Oid			relid;
+					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
+					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+
+					/*
+					 * CREATE STATISTICS will influence future execution plans
+					 * but does not interfere with currently executing plans.
+					 * So it should be enough to take ShareUpdateExclusiveLock
+					 * on relation, conflicting with ANALYZE and other DDL that
+					 * sets statistical information, but not with normal queries.
+					 *
+					 * XXX RangeVarCallbackOwnsRelation not needed needed here,
+					 * to keep the same behavior as before.
+					 */
+					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+
+					/* Run parse analysis ... */
+					stmt = transformStatsStmt(relid, stmt, queryString);
+
+					address = CreateStatistics(stmt);
+				}
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index db803b4388..c58bef73d7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -336,7 +336,8 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									bool attrsOnly, bool keysOnly,
 									bool showTblSpc, bool inherits,
 									int prettyFlags, bool missing_ok);
-static char *pg_get_statisticsobj_worker(Oid statextid, bool missing_ok);
+static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only,
+										 bool missing_ok);
 static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 									  bool attrsOnly, bool missing_ok);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
@@ -1507,7 +1508,26 @@ pg_get_statisticsobjdef(PG_FUNCTION_ARGS)
 	Oid			statextid = PG_GETARG_OID(0);
 	char	   *res;
 
-	res = pg_get_statisticsobj_worker(statextid, true);
+	res = pg_get_statisticsobj_worker(statextid, false, true);
+
+	if (res == NULL)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(string_to_text(res));
+}
+
+
+/*
+ * pg_get_statisticsobjdef_columns
+ *		Get columns and expressions for an extended statistics object
+ */
+Datum
+pg_get_statisticsobjdef_columns(PG_FUNCTION_ARGS)
+{
+	Oid			statextid = PG_GETARG_OID(0);
+	char	   *res;
+
+	res = pg_get_statisticsobj_worker(statextid, true, true);
 
 	if (res == NULL)
 		PG_RETURN_NULL();
@@ -1519,7 +1539,7 @@ pg_get_statisticsobjdef(PG_FUNCTION_ARGS)
  * Internal workhorse to decompile an extended statistics object.
  */
 static char *
-pg_get_statisticsobj_worker(Oid statextid, bool missing_ok)
+pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok)
 {
 	Form_pg_statistic_ext statextrec;
 	HeapTuple	statexttup;
@@ -1534,6 +1554,11 @@ pg_get_statisticsobj_worker(Oid statextid, bool missing_ok)
 	bool		dependencies_enabled;
 	bool		mcv_enabled;
 	int			i;
+	List	   *context;
+	ListCell   *lc;
+	List	   *exprs = NIL;
+	bool		has_exprs;
+	int			ncolumns;
 
 	statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid));
 
@@ -1544,75 +1569,132 @@ pg_get_statisticsobj_worker(Oid statextid, bool missing_ok)
 		elog(ERROR, "cache lookup failed for statistics object %u", statextid);
 	}
 
-	statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
-
-	initStringInfo(&buf);
+	/* has the statistics expressions? */
+	has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL);
 
-	nsp = get_namespace_name(statextrec->stxnamespace);
-	appendStringInfo(&buf, "CREATE STATISTICS %s",
-					 quote_qualified_identifier(nsp,
-												NameStr(statextrec->stxname)));
+	statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
 
 	/*
-	 * Decode the stxkind column so that we know which stats types to print.
+	 * Get the statistics expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions and predicate, because we want
+	 * to display non-const-folded expressions.)
 	 */
-	datum = SysCacheGetAttr(STATEXTOID, statexttup,
-							Anum_pg_statistic_ext_stxkind, &isnull);
-	Assert(!isnull);
-	arr = DatumGetArrayTypeP(datum);
-	if (ARR_NDIM(arr) != 1 ||
-		ARR_HASNULL(arr) ||
-		ARR_ELEMTYPE(arr) != CHAROID)
-		elog(ERROR, "stxkind is not a 1-D char array");
-	enabled = (char *) ARR_DATA_PTR(arr);
+	if (has_exprs)
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(STATEXTOID, statexttup,
+									 Anum_pg_statistic_ext_stxexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		exprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
 
-	ndistinct_enabled = false;
-	dependencies_enabled = false;
-	mcv_enabled = false;
+		/*
+		 * Run the expressions through eval_const_expressions. This is not just an
+		 * optimization, but is necessary, because the planner will be comparing
+		 * them to similarly-processed qual clauses, and may fail to detect valid
+		 * matches without this.  We must not use canonicalize_qual, however,
+		 * since these aren't qual expressions.
+		 *
+		 * XXX Not sure if this is really needed, it's not in pg_get_indexdef. In
+		 * fact the comment above suggests we don't want const-folding here.
+		 */
+		// exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
+
+		/*
+		 * May as well fix opfuncids too
+		 *
+		 * XXX Same here. Is this something we want/need?
+		 */
+		// fix_opfuncids((Node *) exprs);
 
-	for (i = 0; i < ARR_DIMS(arr)[0]; i++)
-	{
-		if (enabled[i] == STATS_EXT_NDISTINCT)
-			ndistinct_enabled = true;
-		if (enabled[i] == STATS_EXT_DEPENDENCIES)
-			dependencies_enabled = true;
-		if (enabled[i] == STATS_EXT_MCV)
-			mcv_enabled = true;
 	}
+	else
+		exprs = NIL;
 
-	/*
-	 * If any option is disabled, then we'll need to append the types clause
-	 * to show which options are enabled.  We omit the types clause on purpose
-	 * when all options are enabled, so a pg_dump/pg_restore will create all
-	 * statistics types on a newer postgres version, if the statistics had all
-	 * options enabled on the original version.
-	 */
-	if (!ndistinct_enabled || !dependencies_enabled || !mcv_enabled)
+	/* count the number of columns (attributes and expressions) */
+	ncolumns = statextrec->stxkeys.dim1 + list_length(exprs);
+
+	initStringInfo(&buf);
+
+	if (!columns_only)
 	{
-		bool		gotone = false;
+		nsp = get_namespace_name(statextrec->stxnamespace);
+		appendStringInfo(&buf, "CREATE STATISTICS %s",
+						 quote_qualified_identifier(nsp,
+													NameStr(statextrec->stxname)));
 
-		appendStringInfoString(&buf, " (");
+		/*
+		 * Decode the stxkind column so that we know which stats types to print.
+		 */
+		datum = SysCacheGetAttr(STATEXTOID, statexttup,
+								Anum_pg_statistic_ext_stxkind, &isnull);
+		Assert(!isnull);
+		arr = DatumGetArrayTypeP(datum);
+		if (ARR_NDIM(arr) != 1 ||
+			ARR_HASNULL(arr) ||
+			ARR_ELEMTYPE(arr) != CHAROID)
+			elog(ERROR, "stxkind is not a 1-D char array");
+		enabled = (char *) ARR_DATA_PTR(arr);
+
+		ndistinct_enabled = false;
+		dependencies_enabled = false;
+		mcv_enabled = false;
 
-		if (ndistinct_enabled)
+		for (i = 0; i < ARR_DIMS(arr)[0]; i++)
 		{
-			appendStringInfoString(&buf, "ndistinct");
-			gotone = true;
+			if (enabled[i] == STATS_EXT_NDISTINCT)
+				ndistinct_enabled = true;
+			if (enabled[i] == STATS_EXT_DEPENDENCIES)
+				dependencies_enabled = true;
+			if (enabled[i] == STATS_EXT_MCV)
+				mcv_enabled = true;
+
+			/* ignore STATS_EXT_EXPRESSIONS (it's built automatically) */
 		}
 
-		if (dependencies_enabled)
+		/*
+		 * If any option is disabled, then we'll need to append the types clause
+		 * to show which options are enabled.  We omit the types clause on purpose
+		 * when all options are enabled, so a pg_dump/pg_restore will create all
+		 * statistics types on a newer postgres version, if the statistics had all
+		 * options enabled on the original version.
+		 *
+		 * But if the statistics is defined on just a single column, it has to be
+		 * an expression statistics. In that case we don't need to specify kinds.
+		 */
+		if ((!ndistinct_enabled || !dependencies_enabled || !mcv_enabled) &&
+			(ncolumns > 1))
 		{
-			appendStringInfo(&buf, "%sdependencies", gotone ? ", " : "");
-			gotone = true;
-		}
+			bool		gotone = false;
 
-		if (mcv_enabled)
-			appendStringInfo(&buf, "%smcv", gotone ? ", " : "");
+			appendStringInfoString(&buf, " (");
 
-		appendStringInfoChar(&buf, ')');
-	}
+			if (ndistinct_enabled)
+			{
+				appendStringInfoString(&buf, "ndistinct");
+				gotone = true;
+			}
 
-	appendStringInfoString(&buf, " ON ");
+			if (dependencies_enabled)
+			{
+				appendStringInfo(&buf, "%sdependencies", gotone ? ", " : "");
+				gotone = true;
+			}
+
+			if (mcv_enabled)
+				appendStringInfo(&buf, "%smcv", gotone ? ", " : "");
+
+			appendStringInfoChar(&buf, ')');
+		}
+
+		appendStringInfoString(&buf, " ON ");
+	}
 
+	/* decode simple column references */
 	for (colno = 0; colno < statextrec->stxkeys.dim1; colno++)
 	{
 		AttrNumber	attnum = statextrec->stxkeys.values[colno];
@@ -1626,14 +1708,109 @@ pg_get_statisticsobj_worker(Oid statextid, bool missing_ok)
 		appendStringInfoString(&buf, quote_identifier(attname));
 	}
 
-	appendStringInfo(&buf, " FROM %s",
-					 generate_relation_name(statextrec->stxrelid, NIL));
+	context = deparse_context_for(get_relation_name(statextrec->stxrelid),
+								  statextrec->stxrelid);
+
+	foreach (lc, exprs)
+	{
+		Node	   *expr = (Node *) lfirst(lc);
+		char	   *str;
+		int			prettyFlags = PRETTYFLAG_INDENT;
+
+		str = deparse_expression_pretty(expr, context, false, false,
+										prettyFlags, 0);
+
+		if (colno > 0)
+			appendStringInfoString(&buf, ", ");
+
+		/* Need parens if it's not a bare function call */
+		if (looks_like_function(expr))
+			appendStringInfoString(&buf, str);
+		else
+			appendStringInfo(&buf, "(%s)", str);
+
+		colno++;
+	}
+
+	if (!columns_only)
+		appendStringInfo(&buf, " FROM %s",
+						 generate_relation_name(statextrec->stxrelid, NIL));
 
 	ReleaseSysCache(statexttup);
 
 	return buf.data;
 }
 
+/*
+ * Generate text array of expressions for statistics object.
+ */
+Datum
+pg_get_statisticsobjdef_expressions(PG_FUNCTION_ARGS)
+{
+	Oid			statextid = PG_GETARG_OID(0);
+	Form_pg_statistic_ext statextrec;
+	HeapTuple	statexttup;
+	Datum		datum;
+	bool		isnull;
+	List	   *context;
+	ListCell   *lc;
+	List	   *exprs = NIL;
+	bool		has_exprs;
+	char	   *tmp;
+	ArrayBuildState *astate = NULL;
+
+	statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid));
+
+	if (!HeapTupleIsValid(statexttup))
+		elog(ERROR, "cache lookup failed for statistics object %u", statextid);
+
+	/* has the statistics expressions? */
+	has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL);
+
+	/* no expressions? we're done */
+	if (!has_exprs)
+	{
+		ReleaseSysCache(statexttup);
+		PG_RETURN_NULL();
+	}
+
+	statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
+
+	/*
+	 * Get the statistics expressions, and deparse them into text values.
+	 */
+	datum = SysCacheGetAttr(STATEXTOID, statexttup,
+									 Anum_pg_statistic_ext_stxexprs, &isnull);
+
+	Assert(!isnull);
+	tmp = TextDatumGetCString(datum);
+	exprs = (List *) stringToNode(tmp);
+	pfree(tmp);
+
+	context = deparse_context_for(get_relation_name(statextrec->stxrelid),
+								  statextrec->stxrelid);
+
+	foreach (lc, exprs)
+	{
+		Node	   *expr = (Node *) lfirst(lc);
+		char	   *str;
+		int			prettyFlags = PRETTYFLAG_INDENT;
+
+		str = deparse_expression_pretty(expr, context, false, false,
+										prettyFlags, 0);
+
+		astate = accumArrayResult(astate,
+								  PointerGetDatum(cstring_to_text(str)),
+								  false,
+								  TEXTOID,
+								  CurrentMemoryContext);
+	}
+
+	ReleaseSysCache(statexttup);
+
+	PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
+}
+
 /*
  * pg_get_partkeydef
  *
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d5e61664bc..5c2eef42d1 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -3291,6 +3291,88 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
 	return varinfos;
 }
 
+/*
+ * Helper routine for estimate_num_groups: add an item to a list of
+ * GrouExprInfos, but only if it's not known equal to any of the existing
+ * entries.
+ */
+typedef struct
+{
+	Node	   *expr;			/* expression */
+	RelOptInfo *rel;			/* relation it belongs to */
+	List	   *varinfos;		/* info for variables in this expression */
+} GroupExprInfo;
+
+static List *
+add_unique_group_expr(PlannerInfo *root, List *exprinfos,
+					 Node *expr, List *vars)
+{
+	GroupExprInfo *exprinfo;
+	ListCell   *lc;
+	Bitmapset  *varnos;
+	Index		varno;
+
+	foreach(lc, exprinfos)
+	{
+		exprinfo = (GroupExprInfo *) lfirst(lc);
+
+		/* Drop exact duplicates */
+		if (equal(expr, exprinfo->expr))
+			return exprinfos;
+	}
+
+	exprinfo = (GroupExprInfo *) palloc(sizeof(GroupExprInfo));
+
+	varnos = pull_varnos(expr);
+
+	/*
+	 * Expressions with vars from multiple relations should never get
+	 * here, as we split them to vars.
+	 */
+	Assert(bms_num_members(varnos) == 1);
+
+	varno = bms_singleton_member(varnos);
+
+	exprinfo->expr = expr;
+	exprinfo->varinfos = NIL;
+	exprinfo->rel = root->simple_rel_array[varno];
+
+	Assert(exprinfo->rel);
+
+	/* Track vars for this expression. */
+	foreach (lc, vars)
+	{
+		VariableStatData vardata;
+		Node *var = (Node *) lfirst(lc);
+
+		/* can we get no vardata for the variable? */
+		examine_variable(root, var, 0, &vardata);
+
+		exprinfo->varinfos
+			= add_unique_group_var(root, exprinfo->varinfos, var, &vardata);
+
+		ReleaseVariableStats(vardata);
+	}
+
+	/* without a list of variables, use the expression itself */
+	if (vars == NIL)
+	{
+		VariableStatData vardata;
+
+		/* can we get no vardata for the variable? */
+		examine_variable(root, expr, 0, &vardata);
+
+		exprinfo->varinfos
+			= add_unique_group_var(root, exprinfo->varinfos,
+								   expr, &vardata);
+
+		ReleaseVariableStats(vardata);
+	}
+
+	return lappend(exprinfos, exprinfo);
+}
+
+
 /*
  * estimate_num_groups		- Estimate number of groups in a grouped query
  *
@@ -3360,7 +3442,7 @@ double
 estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 					List **pgset)
 {
-	List	   *varinfos = NIL;
+	List	   *exprinfos = NIL;
 	double		srf_multiplier = 1.0;
 	double		numdistinct;
 	ListCell   *l;
@@ -3398,6 +3480,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 		double		this_srf_multiplier;
 		VariableStatData vardata;
 		List	   *varshere;
+		Relids		varnos;
 		ListCell   *l2;
 
 		/* is expression in this grouping set? */
@@ -3434,8 +3517,9 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 		examine_variable(root, groupexpr, 0, &vardata);
 		if (HeapTupleIsValid(vardata.statsTuple) || vardata.isunique)
 		{
-			varinfos = add_unique_group_var(root, varinfos,
-											groupexpr, &vardata);
+			exprinfos = add_unique_group_expr(root, exprinfos,
+											  groupexpr, NIL);
+
 			ReleaseVariableStats(vardata);
 			continue;
 		}
@@ -3465,6 +3549,19 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 			continue;
 		}
 
+		/*
+		 * Are all the variables from the same relation? If yes, search for
+		 * an extended statistic matching this expression exactly.
+		 */
+		varnos = pull_varnos((Node *) varshere);
+		if (bms_membership(varnos) == BMS_SINGLETON)
+		{
+			exprinfos = add_unique_group_expr(root, exprinfos,
+											  groupexpr,
+											  varshere);
+			continue;
+		}
+
 		/*
 		 * Else add variables to varinfos list
 		 */
@@ -3472,9 +3569,8 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 		{
 			Node	   *var = (Node *) lfirst(l2);
 
-			examine_variable(root, var, 0, &vardata);
-			varinfos = add_unique_group_var(root, varinfos, var, &vardata);
-			ReleaseVariableStats(vardata);
+			exprinfos = add_unique_group_expr(root, exprinfos,
+											  var, NIL);
 		}
 	}
 
@@ -3482,7 +3578,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 	 * If now no Vars, we must have an all-constant or all-boolean GROUP BY
 	 * list.
 	 */
-	if (varinfos == NIL)
+	if (exprinfos == NIL)
 	{
 		/* Apply SRF multiplier as we would do in the long path */
 		numdistinct *= srf_multiplier;
@@ -3506,32 +3602,32 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 	 */
 	do
 	{
-		GroupVarInfo *varinfo1 = (GroupVarInfo *) linitial(varinfos);
-		RelOptInfo *rel = varinfo1->rel;
+		GroupExprInfo *exprinfo1 = (GroupExprInfo *) linitial(exprinfos);
+		RelOptInfo *rel = exprinfo1->rel;
 		double		reldistinct = 1;
 		double		relmaxndistinct = reldistinct;
 		int			relvarcount = 0;
-		List	   *newvarinfos = NIL;
-		List	   *relvarinfos = NIL;
+		List	   *newexprinfos = NIL;
+		List	   *relexprinfos = NIL;
 
 		/*
 		 * Split the list of varinfos in two - one for the current rel, one
 		 * for remaining Vars on other rels.
 		 */
-		relvarinfos = lappend(relvarinfos, varinfo1);
-		for_each_from(l, varinfos, 1)
+		relexprinfos = lappend(relexprinfos, exprinfo1);
+		for_each_from(l, exprinfos, 1)
 		{
-			GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(l);
+			GroupExprInfo *exprinfo2 = (GroupExprInfo *) lfirst(l);
 
-			if (varinfo2->rel == varinfo1->rel)
+			if (exprinfo2->rel == exprinfo1->rel)
 			{
 				/* varinfos on current rel */
-				relvarinfos = lappend(relvarinfos, varinfo2);
+				relexprinfos = lappend(relexprinfos, exprinfo2);
 			}
 			else
 			{
-				/* not time to process varinfo2 yet */
-				newvarinfos = lappend(newvarinfos, varinfo2);
+				/* not time to process exprinfo2 yet */
+				newexprinfos = lappend(newexprinfos, exprinfo2);
 			}
 		}
 
@@ -3547,11 +3643,11 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 		 * apply.  We apply a fudge factor below, but only if we multiplied
 		 * more than one such values.
 		 */
-		while (relvarinfos)
+		while (relexprinfos)
 		{
 			double		mvndistinct;
 
-			if (estimate_multivariate_ndistinct(root, rel, &relvarinfos,
+			if (estimate_multivariate_ndistinct(root, rel, &relexprinfos,
 												&mvndistinct))
 			{
 				reldistinct *= mvndistinct;
@@ -3561,18 +3657,24 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 			}
 			else
 			{
-				foreach(l, relvarinfos)
+				foreach(l, relexprinfos)
 				{
-					GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(l);
+					ListCell *lc;
+					GroupExprInfo *exprinfo2 = (GroupExprInfo *) lfirst(l);
+
+					foreach (lc, exprinfo2->varinfos)
+					{
+						GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(lc);
 
-					reldistinct *= varinfo2->ndistinct;
-					if (relmaxndistinct < varinfo2->ndistinct)
-						relmaxndistinct = varinfo2->ndistinct;
-					relvarcount++;
+						reldistinct *= varinfo2->ndistinct;
+						if (relmaxndistinct < varinfo2->ndistinct)
+							relmaxndistinct = varinfo2->ndistinct;
+						relvarcount++;
+					}
 				}
 
 				/* we're done with this relation */
-				relvarinfos = NIL;
+				relexprinfos = NIL;
 			}
 		}
 
@@ -3658,8 +3760,8 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 			numdistinct *= reldistinct;
 		}
 
-		varinfos = newvarinfos;
-	} while (varinfos != NIL);
+		exprinfos = newexprinfos;
+	} while (exprinfos != NIL);
 
 	/* Now we can account for the effects of any SRFs */
 	numdistinct *= srf_multiplier;
@@ -3877,53 +3979,75 @@ estimate_hashagg_tablesize(PlannerInfo *root, Path *path,
  */
 static bool
 estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
-								List **varinfos, double *ndistinct)
+								List **exprinfos, double *ndistinct)
 {
 	ListCell   *lc;
-	Bitmapset  *attnums = NULL;
-	int			nmatches;
+	int			nmatches_vars;
+	int			nmatches_exprs;
 	Oid			statOid = InvalidOid;
 	MVNDistinct *stats;
-	Bitmapset  *matched = NULL;
+	StatisticExtInfo *matched_info = NULL;
 
 	/* bail out immediately if the table has no extended statistics */
 	if (!rel->statlist)
 		return false;
 
-	/* Determine the attnums we're looking for */
-	foreach(lc, *varinfos)
-	{
-		GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc);
-		AttrNumber	attnum;
-
-		Assert(varinfo->rel == rel);
-
-		if (!IsA(varinfo->var, Var))
-			continue;
-
-		attnum = ((Var *) varinfo->var)->varattno;
-
-		if (!AttrNumberIsForUserDefinedAttr(attnum))
-			continue;
-
-		attnums = bms_add_member(attnums, attnum);
-	}
-
 	/* look for the ndistinct statistics matching the most vars */
-	nmatches = 1;				/* we require at least two matches */
+	nmatches_vars = 0;				/* we require at least two matches */
+	nmatches_exprs = 0;
 	foreach(lc, rel->statlist)
 	{
+		ListCell	*lc2;
 		StatisticExtInfo *info = (StatisticExtInfo *) lfirst(lc);
-		Bitmapset  *shared;
-		int			nshared;
+		int			nshared_vars = 0;
+		int			nshared_exprs = 0;
 
 		/* skip statistics of other kinds */
 		if (info->kind != STATS_EXT_NDISTINCT)
 			continue;
 
-		/* compute attnums shared by the vars and the statistics object */
-		shared = bms_intersect(info->keys, attnums);
-		nshared = bms_num_members(shared);
+		/*
+		 * Determine how many expressions (and variables in non-matched
+		 * expressions) match.
+		 */
+		foreach(lc2, *exprinfos)
+		{
+			ListCell *lc3;
+			GroupExprInfo *exprinfo = (GroupExprInfo *) lfirst(lc2);
+			AttrNumber	attnum;
+
+			Assert(exprinfo->rel == rel);
+
+			/* simple Var, search in statistics keys directly */
+			if (IsA(exprinfo->expr, Var))
+			{
+				attnum = ((Var *) exprinfo->expr)->varattno;
+
+				if (!AttrNumberIsForUserDefinedAttr(attnum))
+					continue;
+
+				if (bms_is_member(attnum, info->keys))
+					nshared_vars++;
+
+				continue;
+			}
+
+			/* expression - see if it's in the statistics */
+			foreach (lc3, info->exprs)
+			{
+				Node *expr = (Node *) lfirst(lc3);
+
+				if (equal(exprinfo->expr, expr))
+				{
+					nshared_exprs++;
+					nshared_vars += list_length(exprinfo->varinfos);
+					break;
+				}
+			}
+		}
+
+		if (nshared_vars + nshared_exprs < 2)
+			continue;
 
 		/*
 		 * Does this statistics object match more columns than the currently
@@ -3932,18 +4056,21 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
 		 * XXX This should break ties using name of the object, or something
 		 * like that, to make the outcome stable.
 		 */
-		if (nshared > nmatches)
+		if ((nshared_vars > nmatches_vars) ||
+			((nshared_vars == nmatches_vars) && (nshared_exprs > nmatches_exprs)))
 		{
 			statOid = info->statOid;
-			nmatches = nshared;
-			matched = shared;
+			nmatches_vars = nshared_vars;
+			nmatches_exprs = nshared_exprs;
+			matched_info = info;
 		}
 	}
 
 	/* No match? */
 	if (statOid == InvalidOid)
 		return false;
-	Assert(nmatches > 1 && matched != NULL);
+
+	Assert(nmatches_vars + nmatches_exprs > 1);
 
 	stats = statext_ndistinct_load(statOid);
 
@@ -3956,6 +4083,56 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
 		int			i;
 		List	   *newlist = NIL;
 		MVNDistinctItem *item = NULL;
+		ListCell   *lc2;
+		Bitmapset  *matched = NULL;
+
+		/* see what actually matched */
+		foreach (lc2, *exprinfos)
+		{
+			ListCell   *lc3;
+			int			idx;
+			bool		found = false;
+
+			GroupExprInfo *exprinfo = (GroupExprInfo *) lfirst(lc2);
+
+			/* expression - see if it's in the statistics */
+			idx = 0;
+			foreach (lc3, matched_info->exprs)
+			{
+				Node *expr = (Node *) lfirst(lc3);
+
+				idx++;
+
+				if (equal(exprinfo->expr, expr))
+				{
+					matched = bms_add_member(matched, MaxHeapAttributeNumber + idx);
+					found = true;
+					break;
+				}
+			}
+
+			if (found)
+				continue;
+
+			foreach (lc3, exprinfo->varinfos)
+			{
+				GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc2);
+
+				/* simple Var, search in statistics keys directly */
+				if (IsA(varinfo->var, Var))
+				{
+					AttrNumber	attnum = ((Var *) varinfo->var)->varattno;
+
+					if (!AttrNumberIsForUserDefinedAttr(attnum))
+						continue;
+
+					if (!bms_is_member(attnum, matched_info->keys))
+						continue;
+
+					matched = bms_add_member(matched, attnum);
+				}
+			}
+		}
 
 		/* Find the specific item that exactly matches the combination */
 		for (i = 0; i < stats->nitems; i++)
@@ -3973,28 +4150,49 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
 		if (!item)
 			elog(ERROR, "corrupt MVNDistinct entry");
 
-		/* Form the output varinfo list, keeping only unmatched ones */
-		foreach(lc, *varinfos)
+		/* Form the output exprinfo list, keeping only unmatched ones */
+		foreach(lc, *exprinfos)
 		{
-			GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc);
+			GroupExprInfo *exprinfo = (GroupExprInfo *) lfirst(lc);
 			AttrNumber	attnum;
+			ListCell   *lc3;
+			bool		found = false;
+
+			foreach (lc3, matched_info->exprs)
+			{
+				Node *expr = (Node *) lfirst(lc3);
+
+				if (equal(exprinfo->expr, expr))
+				{
+					found = true;
+					break;
+				}
+			}
+
+			/* the whole expression was matched, so skip it */
+			if (found)
+				continue;
 
-			if (!IsA(varinfo->var, Var))
+			if (!IsA(exprinfo->expr, Var))
 			{
-				newlist = lappend(newlist, varinfo);
+				/*
+				 * FIXME Probably should remove varinfos that match the
+				 * selected MVNDistinct item.
+				 */
+				newlist = lappend(newlist, exprinfo);
 				continue;
 			}
 
-			attnum = ((Var *) varinfo->var)->varattno;
+			attnum = ((Var *) exprinfo->expr)->varattno;
 
 			if (!AttrNumberIsForUserDefinedAttr(attnum))
 				continue;
 
 			if (!bms_is_member(attnum, matched))
-				newlist = lappend(newlist, varinfo);
+				newlist = lappend(newlist, exprinfo);
 		}
 
-		*varinfos = newlist;
+		*exprinfos = newlist;
 		*ndistinct = item->ndistinct;
 		return true;
 	}
@@ -4690,6 +4888,13 @@ get_join_variables(PlannerInfo *root, List *args, SpecialJoinInfo *sjinfo,
 		*join_is_reversed = false;
 }
 
+/* statext_expressions_load copies the tuple, so just pfree it. */
+static void
+ReleaseDummy(HeapTuple tuple)
+{
+	pfree(tuple);
+}
+
 /*
  * examine_variable
  *		Try to look up statistical data about an expression.
@@ -4830,6 +5035,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 		 * operator we are estimating for.  FIXME later.
 		 */
 		ListCell   *ilist;
+		ListCell   *slist;
 
 		foreach(ilist, onerel->indexlist)
 		{
@@ -4986,6 +5192,67 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 			if (vardata->statsTuple)
 				break;
 		}
+
+		/*
+		 * Search extended statistics for one with a matching expression.
+		 * There might be multiple ones, so just grab the first one. In
+		 * the future, we might consider
+		 */
+		foreach(slist, onerel->statlist)
+		{
+			StatisticExtInfo *info = (StatisticExtInfo *) lfirst(slist);
+			ListCell   *expr_item;
+			int			pos;
+
+			/*
+			 * Stop once we've found statistics for the expression (either
+			 * from extended stats, or for an index in the preceding loop).
+			 */
+			if (vardata->statsTuple)
+				break;
+
+			/* skip stats without per-expression stats */
+			if (info->kind != STATS_EXT_EXPRESSIONS)
+				continue;
+
+			pos = 0;
+			foreach (expr_item, info->exprs)
+			{
+				Node *expr = (Node *) lfirst(expr_item);
+
+				Assert(expr);
+
+				/* strip RelabelType before comparing it */
+				if (expr && IsA(expr, RelabelType))
+					expr = (Node *) ((RelabelType *) expr)->arg;
+
+				/* found a match, see if we can extract pg_statistic row */
+				if (equal(node, expr))
+				{
+					HeapTuple t = statext_expressions_load(info->statOid, pos);
+
+					vardata->statsTuple = t;
+
+					/*
+					 * FIXME not sure if we should cache the tuple somewhere?
+					 * It's stored in a cached tuple in the "data" catalog,
+					 * and we just create a new copy every time.
+					 */
+					vardata->freefunc = ReleaseDummy;
+
+					/*
+					 * FIXME Hack to make statistic_proc_security_check happy,
+					 * so that this does not get rejected. Probably needs more
+					 * thought, just a hack.
+					 */
+					vardata->acl_ok = true;
+
+					break;
+				}
+
+				pos++;
+			}
+		}
 	}
 }
 
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 11dc98ee0a..a1c9184415 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2591,6 +2591,18 @@ my %tests = (
 		unlike => { exclude_dump_test_schema => 1, },
 	},
 
+	'CREATE STATISTICS extended_stats_expression' => {
+		create_order => 99,
+		create_sql   => 'CREATE STATISTICS dump_test.test_ext_stats_expr
+							ON (2 * col1) FROM dump_test.test_fifth_table',
+		regexp => qr/^
+			\QCREATE STATISTICS dump_test.test_ext_stats_expr ON ((2 * col1)) FROM dump_test.test_fifth_table;\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/describe.c b/src/bin/psql/describe.c
index caf97563f4..244bbb5f82 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2680,15 +2680,16 @@ describeOneTableDetails(const char *schemaname,
 		/* print any extended statistics */
 		if (pset.sversion >= 100000)
 		{
+			/*
+			 * FIXME this needs to be version-dependent, because older
+			 * versions don't have pg_get_statisticsobjdef_columns.
+			 */
 			printfPQExpBuffer(&buf,
 							  "SELECT oid, "
 							  "stxrelid::pg_catalog.regclass, "
 							  "stxnamespace::pg_catalog.regnamespace AS nsp, "
 							  "stxname,\n"
-							  "  (SELECT pg_catalog.string_agg(pg_catalog.quote_ident(attname),', ')\n"
-							  "   FROM pg_catalog.unnest(stxkeys) s(attnum)\n"
-							  "   JOIN pg_catalog.pg_attribute a ON (stxrelid = a.attrelid AND\n"
-							  "        a.attnum = s.attnum AND NOT attisdropped)) AS columns,\n"
+							  "pg_get_statisticsobjdef_columns(oid) AS columns,\n"
 							  "  'd' = any(stxkind) AS ndist_enabled,\n"
 							  "  'f' = any(stxkind) AS deps_enabled,\n"
 							  "  'm' = any(stxkind) AS mcv_enabled,\n");
@@ -2715,33 +2716,60 @@ describeOneTableDetails(const char *schemaname,
 				for (i = 0; i < tuples; i++)
 				{
 					bool		gotone = false;
+					bool		has_ndistinct;
+					bool		has_dependencies;
+					bool		has_mcv;
+					bool		has_all;
+					bool		has_some;
+
+					has_ndistinct = (strcmp(PQgetvalue(result, i, 5), "t") == 0);
+					has_dependencies = (strcmp(PQgetvalue(result, i, 6), "t") == 0);
+					has_mcv = (strcmp(PQgetvalue(result, i, 7), "t") == 0);
 
 					printfPQExpBuffer(&buf, "    ");
 
 					/* statistics object name (qualified with namespace) */
-					appendPQExpBuffer(&buf, "\"%s\".\"%s\" (",
+					appendPQExpBuffer(&buf, "\"%s\".\"%s\"",
 									  PQgetvalue(result, i, 2),
 									  PQgetvalue(result, i, 3));
 
-					/* options */
-					if (strcmp(PQgetvalue(result, i, 5), "t") == 0)
-					{
-						appendPQExpBufferStr(&buf, "ndistinct");
-						gotone = true;
-					}
+					/*
+					 * When printing kinds we ignore expression statistics, which
+					 * is used only internally and can't be specified by user.
+					 * We don't print the kinds when either none are specified
+					 * (in which case it has to be statistics on a single expr)
+					 * or when all are specified (in which case we assume it's
+					 * expanded by CREATE STATISTICS).
+					 */
+					has_all = (has_ndistinct && has_dependencies && has_mcv);
+					has_some = (has_ndistinct || has_dependencies || has_mcv);
 
-					if (strcmp(PQgetvalue(result, i, 6), "t") == 0)
+					if (has_some && !has_all)
 					{
-						appendPQExpBuffer(&buf, "%sdependencies", gotone ? ", " : "");
-						gotone = true;
-					}
+						appendPQExpBuffer(&buf, " (");
 
-					if (strcmp(PQgetvalue(result, i, 7), "t") == 0)
-					{
-						appendPQExpBuffer(&buf, "%smcv", gotone ? ", " : "");
+						/* options */
+						if (has_ndistinct)
+						{
+							appendPQExpBufferStr(&buf, "ndistinct");
+							gotone = true;
+						}
+
+						if (has_dependencies)
+						{
+							appendPQExpBuffer(&buf, "%sdependencies", gotone ? ", " : "");
+							gotone = true;
+						}
+
+						if (has_mcv)
+						{
+							appendPQExpBuffer(&buf, "%smcv", gotone ? ", " : "");
+						}
+
+						appendPQExpBuffer(&buf, ")");
 					}
 
-					appendPQExpBuffer(&buf, ") ON %s FROM %s",
+					appendPQExpBuffer(&buf, " ON %s FROM %s",
 									  PQgetvalue(result, i, 4),
 									  PQgetvalue(result, i, 1));
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d7b55f57ea..78edde6ec9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3652,6 +3652,14 @@
   proname => 'pg_get_statisticsobjdef', provolatile => 's',
   prorettype => 'text', proargtypes => 'oid',
   prosrc => 'pg_get_statisticsobjdef' },
+{ oid => '8887', descr => 'extended statistics columns',
+  proname => 'pg_get_statisticsobjdef_columns', provolatile => 's',
+  prorettype => 'text', proargtypes => 'oid',
+  prosrc => 'pg_get_statisticsobjdef_columns' },
+{ oid => '8886', descr => 'extended statistics expressions',
+  proname => 'pg_get_statisticsobjdef_expressions', provolatile => 's',
+  prorettype => '_text', proargtypes => 'oid',
+  prosrc => 'pg_get_statisticsobjdef_expressions' },
 { oid => '3352', descr => 'partition key description',
   proname => 'pg_get_partkeydef', provolatile => 's', prorettype => 'text',
   proargtypes => 'oid', prosrc => 'pg_get_partkeydef' },
diff --git a/src/include/catalog/pg_statistic_ext.h b/src/include/catalog/pg_statistic_ext.h
index 99f6cea0a5..cf46a79af9 100644
--- a/src/include/catalog/pg_statistic_ext.h
+++ b/src/include/catalog/pg_statistic_ext.h
@@ -52,6 +52,9 @@ CATALOG(pg_statistic_ext,3381,StatisticExtRelationId)
 #ifdef CATALOG_VARLEN
 	char		stxkind[1] BKI_FORCE_NOT_NULL;	/* statistics kinds requested
 												 * to build */
+	pg_node_tree stxexprs;		/* A list of expression trees for stats
+								 * attributes that are not simple column
+								 * references. */
 #endif
 
 } FormData_pg_statistic_ext;
@@ -77,6 +80,7 @@ DECLARE_INDEX(pg_statistic_ext_relid_index, 3379, on pg_statistic_ext using btre
 #define STATS_EXT_NDISTINCT			'd'
 #define STATS_EXT_DEPENDENCIES		'f'
 #define STATS_EXT_MCV				'm'
+#define STATS_EXT_EXPRESSIONS		'e'
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_statistic_ext_data.h b/src/include/catalog/pg_statistic_ext_data.h
index e0aa152f7b..0d2f6a6c32 100644
--- a/src/include/catalog/pg_statistic_ext_data.h
+++ b/src/include/catalog/pg_statistic_ext_data.h
@@ -37,6 +37,7 @@ CATALOG(pg_statistic_ext_data,3429,StatisticExtDataRelationId)
 	pg_ndistinct stxdndistinct; /* ndistinct coefficients (serialized) */
 	pg_dependencies stxddependencies;	/* dependencies (serialized) */
 	pg_mcv_list stxdmcv;		/* MCV (serialized) */
+	pg_statistic stxdexpr[1];		/* stats for expressions */
 
 #endif
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index caed683ba9..374f047dda 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -451,6 +451,7 @@ typedef enum NodeTag
 	T_TypeName,
 	T_ColumnDef,
 	T_IndexElem,
+	T_StatsElem,
 	T_Constraint,
 	T_DefElem,
 	T_RangeTblEntry,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc2bb40926..f2042ba445 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2830,8 +2830,24 @@ typedef struct CreateStatsStmt
 	List	   *relations;		/* rels to build stats on (list of RangeVar) */
 	char	   *stxcomment;		/* comment to apply to stats, or NULL */
 	bool		if_not_exists;	/* do nothing if stats name already exists */
+	bool		transformed;	/* true when transformStatsStmt is finished */
 } CreateStatsStmt;
 
+/*
+ * StatsElem - statistics parameters (used in CREATE STATISTICS)
+ *
+ * For a plain attribute, 'name' is the name of the referenced table column
+ * and 'expr' is NULL.  For an expression, 'name' is NULL and 'expr' is the
+ * expression tree.
+ */
+typedef struct StatsElem
+{
+	NodeTag		type;
+	char	   *name;			/* name of attribute to index, or NULL */
+	Node	   *expr;			/* expression to index, or NULL */
+} StatsElem;
+
+
 /* ----------------------
  *		Alter Statistics Statement
  * ----------------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index cde2637798..7cd1a67896 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -917,6 +917,7 @@ typedef struct StatisticExtInfo
 	RelOptInfo *rel;			/* back-link to statistic's table */
 	char		kind;			/* statistic kind of this entry */
 	Bitmapset  *keys;			/* attnums of the columns covered */
+	List	   *exprs;			/* expressions */
 } StatisticExtInfo;
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index dfc214b06f..2b477c38eb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -69,6 +69,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
 	EXPR_KIND_INDEX_EXPRESSION, /* index expression */
 	EXPR_KIND_INDEX_PREDICATE,	/* index predicate */
+	EXPR_KIND_STATS_EXPRESSION, /* extended statistics expression */
 	EXPR_KIND_ALTER_COL_TRANSFORM,	/* transform expr in ALTER COLUMN TYPE */
 	EXPR_KIND_EXECUTE_PARAMETER,	/* parameter value in EXECUTE */
 	EXPR_KIND_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bfa4a6b0f2..1056bf081b 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,6 +26,8 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
+extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
diff --git a/src/include/statistics/extended_stats_internal.h b/src/include/statistics/extended_stats_internal.h
index c849bd57c0..092bc3eb8a 100644
--- a/src/include/statistics/extended_stats_internal.h
+++ b/src/include/statistics/extended_stats_internal.h
@@ -57,19 +57,35 @@ typedef struct SortItem
 	int			count;
 } SortItem;
 
+/*
+ * Used to pass pre-computed information about expressions the stats
+ * object is defined on.
+ */
+typedef struct ExprInfo
+{
+	int			nexprs;			/* number of expressions */
+	Oid		   *collations;		/* collation for each expression */
+	Oid		   *types;			/* type of each expression */
+	Datum	  **values;			/* values for each expression */
+	bool	  **nulls;			/* nulls for each expression */
+} ExprInfo;
+
 extern MVNDistinct *statext_ndistinct_build(double totalrows,
 											int numrows, HeapTuple *rows,
-											Bitmapset *attrs, VacAttrStats **stats);
+											ExprInfo *exprs, Bitmapset *attrs,
+											VacAttrStats **stats);
 extern bytea *statext_ndistinct_serialize(MVNDistinct *ndistinct);
 extern MVNDistinct *statext_ndistinct_deserialize(bytea *data);
 
 extern MVDependencies *statext_dependencies_build(int numrows, HeapTuple *rows,
-												  Bitmapset *attrs, VacAttrStats **stats);
+												  ExprInfo *exprs, Bitmapset *attrs,
+												  VacAttrStats **stats);
 extern bytea *statext_dependencies_serialize(MVDependencies *dependencies);
 extern MVDependencies *statext_dependencies_deserialize(bytea *data);
 
 extern MCVList *statext_mcv_build(int numrows, HeapTuple *rows,
-								  Bitmapset *attrs, VacAttrStats **stats,
+								  ExprInfo *exprs, Bitmapset *attrs,
+								  VacAttrStats **stats,
 								  double totalrows, int stattarget);
 extern bytea *statext_mcv_serialize(MCVList *mcv, VacAttrStats **stats);
 extern MCVList *statext_mcv_deserialize(bytea *data);
@@ -93,11 +109,18 @@ extern void *bsearch_arg(const void *key, const void *base,
 extern AttrNumber *build_attnums_array(Bitmapset *attrs, int *numattrs);
 
 extern SortItem *build_sorted_items(int numrows, int *nitems, HeapTuple *rows,
-									TupleDesc tdesc, MultiSortSupport mss,
+									ExprInfo *exprs, TupleDesc tdesc,
+									MultiSortSupport mss,
 									int numattrs, AttrNumber *attnums);
 
 extern bool examine_clause_args(List *args, Var **varp,
 								Const **cstp, bool *varonleftp);
+extern bool examine_clause_args2(List *args, Node **exprp,
+								 Const **cstp, bool *expronleftp);
+extern bool examine_opclause_expression(OpExpr *expr, Var **varp, Const **cstp,
+										bool *varonleftp);
+extern bool examine_opclause_expression2(OpExpr *expr, Node **exprp, Const **cstp,
+										 bool *expronleftp);
 
 extern Selectivity mcv_combine_selectivities(Selectivity simple_sel,
 											 Selectivity mcv_sel,
@@ -124,4 +147,13 @@ extern Selectivity mcv_clause_selectivity_or(PlannerInfo *root,
 											 Selectivity *overlap_basesel,
 											 Selectivity *totalsel);
 
+extern Bitmapset *add_expressions_to_attributes(Bitmapset *attrs, int nexprs);
+
+/* translate 0-based expression index to attnum and back */
+#define	EXPRESSION_ATTNUM(index)	\
+	(MaxHeapAttributeNumber + (index) + 1)
+
+#define	EXPRESSION_INDEX(attnum)	\
+	((attnum) - MaxHeapAttributeNumber - 1)
+
 #endif							/* EXTENDED_STATS_INTERNAL_H */
diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h
index fec50688ea..006d578e0c 100644
--- a/src/include/statistics/statistics.h
+++ b/src/include/statistics/statistics.h
@@ -121,6 +121,8 @@ extern Selectivity statext_clauselist_selectivity(PlannerInfo *root,
 extern bool has_stats_of_kind(List *stats, char requiredkind);
 extern StatisticExtInfo *choose_best_statistics(List *stats, char requiredkind,
 												Bitmapset **clause_attnums,
+												List **clause_exprs,
 												int nclauses);
+extern HeapTuple statext_expressions_load(Oid stxoid, int idx);
 
 #endif							/* STATISTICS_H */
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 10d17be23c..4dc5e6aa5f 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -304,7 +304,9 @@ CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);
 CREATE INDEX ctlt1_b_key ON ctlt1 (b);
 CREATE INDEX ctlt1_fnidx ON ctlt1 ((a || b));
 CREATE STATISTICS ctlt1_a_b_stat ON a,b FROM ctlt1;
+CREATE STATISTICS ctlt1_expr_stat ON (a || b) FROM ctlt1;
 COMMENT ON STATISTICS ctlt1_a_b_stat IS 'ab stats';
+COMMENT ON STATISTICS ctlt1_expr_stat IS 'ab expr stats';
 COMMENT ON COLUMN ctlt1.a IS 'A';
 COMMENT ON COLUMN ctlt1.b IS 'B';
 COMMENT ON CONSTRAINT ctlt1_a_check ON ctlt1 IS 't1_a_check';
@@ -414,7 +416,8 @@ Indexes:
 Check constraints:
     "ctlt1_a_check" CHECK (length(a) > 2)
 Statistics objects:
-    "public"."ctlt_all_a_b_stat" (ndistinct, dependencies, mcv) ON a, b FROM ctlt_all
+    "public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
+    "public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
 
 SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
     relname     | objsubid | description 
@@ -424,10 +427,11 @@ SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_clas
 (2 rows)
 
 SELECT s.stxname, objsubid, description FROM pg_description, pg_statistic_ext s WHERE classoid = 'pg_statistic_ext'::regclass AND objoid = s.oid AND s.stxrelid = 'ctlt_all'::regclass ORDER BY s.stxname, objsubid;
-      stxname      | objsubid | description 
--------------------+----------+-------------
- ctlt_all_a_b_stat |        0 | ab stats
-(1 row)
+      stxname       | objsubid |  description  
+--------------------+----------+---------------
+ ctlt_all_a_b_stat  |        0 | ab stats
+ ctlt_all_expr_stat |        0 | ab expr stats
+(2 rows)
 
 CREATE TABLE inh_error1 () INHERITS (ctlt1, ctlt4);
 NOTICE:  merging multiple inherited definitions of column "a"
@@ -452,7 +456,8 @@ Indexes:
 Check constraints:
     "ctlt1_a_check" CHECK (length(a) > 2)
 Statistics objects:
-    "public"."pg_attrdef_a_b_stat" (ndistinct, dependencies, mcv) ON a, b FROM public.pg_attrdef
+    "public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
+    "public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
 
 DROP TABLE public.pg_attrdef;
 -- Check that LIKE isn't confused when new table masks the old, either
@@ -473,7 +478,8 @@ Indexes:
 Check constraints:
     "ctlt1_a_check" CHECK (length(a) > 2)
 Statistics objects:
-    "ctl_schema"."ctlt1_a_b_stat" (ndistinct, dependencies, mcv) ON a, b FROM ctlt1
+    "ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
+    "ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
 
 ROLLBACK;
 DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index a687e99d1e..663cb7b150 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2393,6 +2393,7 @@ pg_stats_ext| SELECT cn.nspname AS schemaname,
     ( SELECT array_agg(a.attname ORDER BY a.attnum) AS array_agg
            FROM (unnest(s.stxkeys) k(k)
              JOIN pg_attribute a ON (((a.attrelid = s.stxrelid) AND (a.attnum = k.k))))) AS attnames,
+    pg_get_statisticsobjdef_expressions(s.oid) AS exprs,
     s.stxkind AS kinds,
     sd.stxdndistinct AS n_distinct,
     sd.stxddependencies AS dependencies,
@@ -2414,6 +2415,80 @@ pg_stats_ext| SELECT cn.nspname AS schemaname,
            FROM (unnest(s.stxkeys) k(k)
              JOIN pg_attribute a ON (((a.attrelid = s.stxrelid) AND (a.attnum = k.k))))
           WHERE (NOT has_column_privilege(c.oid, a.attnum, 'select'::text))))) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_stats_ext_exprs| SELECT cn.nspname AS schemaname,
+    c.relname AS tablename,
+    sn.nspname AS statistics_schemaname,
+    s.stxname AS statistics_name,
+    pg_get_userbyid(s.stxowner) AS statistics_owner,
+    stat.expr,
+    (stat.a).stanullfrac AS null_frac,
+    (stat.a).stawidth AS avg_width,
+    (stat.a).stadistinct AS n_distinct,
+        CASE
+            WHEN ((stat.a).stakind1 = 1) THEN (stat.a).stavalues1
+            WHEN ((stat.a).stakind2 = 1) THEN (stat.a).stavalues2
+            WHEN ((stat.a).stakind3 = 1) THEN (stat.a).stavalues3
+            WHEN ((stat.a).stakind4 = 1) THEN (stat.a).stavalues4
+            WHEN ((stat.a).stakind5 = 1) THEN (stat.a).stavalues5
+            ELSE NULL::anyarray
+        END AS most_common_vals,
+        CASE
+            WHEN ((stat.a).stakind1 = 1) THEN (stat.a).stanumbers1
+            WHEN ((stat.a).stakind2 = 1) THEN (stat.a).stanumbers2
+            WHEN ((stat.a).stakind3 = 1) THEN (stat.a).stanumbers3
+            WHEN ((stat.a).stakind4 = 1) THEN (stat.a).stanumbers4
+            WHEN ((stat.a).stakind5 = 1) THEN (stat.a).stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN ((stat.a).stakind1 = 2) THEN (stat.a).stavalues1
+            WHEN ((stat.a).stakind2 = 2) THEN (stat.a).stavalues2
+            WHEN ((stat.a).stakind3 = 2) THEN (stat.a).stavalues3
+            WHEN ((stat.a).stakind4 = 2) THEN (stat.a).stavalues4
+            WHEN ((stat.a).stakind5 = 2) THEN (stat.a).stavalues5
+            ELSE NULL::anyarray
+        END AS histogram_bounds,
+        CASE
+            WHEN ((stat.a).stakind1 = 3) THEN (stat.a).stanumbers1[1]
+            WHEN ((stat.a).stakind2 = 3) THEN (stat.a).stanumbers2[1]
+            WHEN ((stat.a).stakind3 = 3) THEN (stat.a).stanumbers3[1]
+            WHEN ((stat.a).stakind4 = 3) THEN (stat.a).stanumbers4[1]
+            WHEN ((stat.a).stakind5 = 3) THEN (stat.a).stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN ((stat.a).stakind1 = 4) THEN (stat.a).stavalues1
+            WHEN ((stat.a).stakind2 = 4) THEN (stat.a).stavalues2
+            WHEN ((stat.a).stakind3 = 4) THEN (stat.a).stavalues3
+            WHEN ((stat.a).stakind4 = 4) THEN (stat.a).stavalues4
+            WHEN ((stat.a).stakind5 = 4) THEN (stat.a).stavalues5
+            ELSE NULL::anyarray
+        END AS most_common_elems,
+        CASE
+            WHEN ((stat.a).stakind1 = 4) THEN (stat.a).stanumbers1
+            WHEN ((stat.a).stakind2 = 4) THEN (stat.a).stanumbers2
+            WHEN ((stat.a).stakind3 = 4) THEN (stat.a).stanumbers3
+            WHEN ((stat.a).stakind4 = 4) THEN (stat.a).stanumbers4
+            WHEN ((stat.a).stakind5 = 4) THEN (stat.a).stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN ((stat.a).stakind1 = 5) THEN (stat.a).stanumbers1
+            WHEN ((stat.a).stakind2 = 5) THEN (stat.a).stanumbers2
+            WHEN ((stat.a).stakind3 = 5) THEN (stat.a).stanumbers3
+            WHEN ((stat.a).stakind4 = 5) THEN (stat.a).stanumbers4
+            WHEN ((stat.a).stakind5 = 5) THEN (stat.a).stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM (((((pg_statistic_ext s
+     JOIN pg_class c ON ((c.oid = s.stxrelid)))
+     JOIN pg_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
+     LEFT JOIN pg_namespace cn ON ((cn.oid = c.relnamespace)))
+     LEFT JOIN pg_namespace sn ON ((sn.oid = s.stxnamespace)))
+     LEFT JOIN LATERAL ( SELECT x.expr,
+            x.a
+           FROM ( SELECT unnest(pg_get_statisticsobjdef_expressions(s.oid)) AS expr,
+                    unnest(sd.stxdexpr) AS a) x) stat ON ((sd.stxdexpr IS NOT NULL)));
 pg_tables| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     pg_get_userbyid(c.relowner) AS tableowner,
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 7bfeaf85f0..1d42796922 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -41,14 +41,29 @@ CREATE STATISTICS tst ON a, b FROM nonexistent;
 ERROR:  relation "nonexistent" does not exist
 CREATE STATISTICS tst ON a, b FROM pg_class;
 ERROR:  column "a" does not exist
+CREATE STATISTICS tst ON relname FROM pg_class;
+ERROR:  extended statistics require at least 2 columns
 CREATE STATISTICS tst ON relname, relname, relnatts FROM pg_class;
 ERROR:  duplicate column name in statistics definition
-CREATE STATISTICS tst ON relnatts + relpages FROM pg_class;
-ERROR:  only simple column references are allowed in CREATE STATISTICS
-CREATE STATISTICS tst ON (relpages, reltuples) FROM pg_class;
-ERROR:  only simple column references are allowed in CREATE STATISTICS
+CREATE STATISTICS tst ON relname, relname, relnatts, relname, relname, relnatts, relname, relname, relnatts FROM pg_class;
+ERROR:  cannot have more than 8 columns in statistics
+CREATE STATISTICS tst ON relname, relname, relnatts, relname, relname, (relname || 'x'), (relnatts + 1), (relname || 'x'), (relname || 'x'), (relnatts + 1) FROM pg_class;
+ERROR:  cannot have more than 8 columns in statistics
+CREATE STATISTICS tst ON (relname || 'x'), (relname || 'x'), (relnatts + 1), (relname || 'x'), (relname || 'x'), (relnatts + 1), (relname || 'x'), (relname || 'x'), (relnatts + 1) FROM pg_class;
+ERROR:  cannot have more than 8 columns in statistics
+CREATE STATISTICS tst ON (relname || 'x'), (relname || 'x'), relnatts FROM pg_class;
+ERROR:  duplicate expression in statistics definition
 CREATE STATISTICS tst (unrecognized) ON relname, relnatts FROM pg_class;
 ERROR:  unrecognized statistics kind "unrecognized"
+-- incorrect expressions
+CREATE STATISTICS tst ON relnatts + relpages FROM pg_class; -- missing parentheses
+ERROR:  syntax error at or near "+"
+LINE 1: CREATE STATISTICS tst ON relnatts + relpages FROM pg_class;
+                                          ^
+CREATE STATISTICS tst ON (relpages, reltuples) FROM pg_class; -- tuple expression
+ERROR:  syntax error at or near ","
+LINE 1: CREATE STATISTICS tst ON (relpages, reltuples) FROM pg_class...
+                                          ^
 -- Ensure stats are dropped sanely, and test IF NOT EXISTS while at it
 CREATE TABLE ab1 (a INTEGER, b INTEGER, c INTEGER);
 CREATE STATISTICS IF NOT EXISTS ab1_a_b_stats ON a, b FROM ab1;
@@ -77,7 +92,7 @@ ALTER TABLE ab1 DROP COLUMN a;
  b      | integer |           |          | 
  c      | integer |           |          | 
 Statistics objects:
-    "public"."ab1_b_c_stats" (ndistinct, dependencies, mcv) ON b, c FROM ab1
+    "public"."ab1_b_c_stats" ON b, c FROM ab1
 
 -- Ensure statistics are dropped when table is
 SELECT stxname FROM pg_statistic_ext WHERE stxname LIKE 'ab1%';
@@ -109,7 +124,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0;
  a      | integer |           |          | 
  b      | integer |           |          | 
 Statistics objects:
-    "public"."ab1_a_b_stats" (ndistinct, dependencies, mcv) ON a, b FROM ab1; STATISTICS 0
+    "public"."ab1_a_b_stats" ON a, b FROM ab1; STATISTICS 0
 
 ANALYZE ab1;
 SELECT stxname, stxdndistinct, stxddependencies, stxdmcv
@@ -129,7 +144,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
  a      | integer |           |          |         | plain   |              | 
  b      | integer |           |          |         | plain   |              | 
 Statistics objects:
-    "public"."ab1_a_b_stats" (ndistinct, dependencies, mcv) ON a, b FROM ab1
+    "public"."ab1_a_b_stats" ON a, b FROM ab1
 
 -- partial analyze doesn't build stats either
 ANALYZE ab1 (a);
@@ -148,6 +163,40 @@ CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1;
 ANALYZE ab1;
 DROP TABLE ab1 CASCADE;
 NOTICE:  drop cascades to table ab1c
+-- basic test for statistics on expressions
+CREATE TABLE ab1 (a INTEGER, b INTEGER, c TIMESTAMP, d TIMESTAMPTZ);
+-- expression stats may be built on a single expression column
+CREATE STATISTICS ab1_exprstat_1 ON (a+b) FROM ab1;
+-- with a single expression, we only enable expression statistics
+CREATE STATISTICS ab1_exprstat_2 ON (a+b) FROM ab1;
+SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_2';
+ stxkind 
+---------
+ {e}
+(1 row)
+
+-- adding anything to the expression builds all statistics kinds
+CREATE STATISTICS ab1_exprstat_3 ON (a+b), a FROM ab1;
+SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_3';
+  stxkind  
+-----------
+ {d,f,m,e}
+(1 row)
+
+-- expression must be immutable, but date_trunc on timestamptz is not
+CREATE STATISTICS ab1_exprstat_4 ON date_trunc('day', d) FROM ab1;
+ERROR:  functions in statistics expression must be marked IMMUTABLE
+-- but on timestamp it should work fine
+CREATE STATISTICS ab1_exprstat_5 ON date_trunc('day', c) FROM ab1;
+-- insert some data and run analyze, to test that these cases build properly
+INSERT INTO ab1
+SELECT
+    generate_series(1,10),
+    generate_series(1,10),
+    generate_series('2020-10-01'::timestamp, '2020-10-10'::timestamp, interval '1 day'),
+    generate_series('2020-10-01'::timestamptz, '2020-10-10'::timestamptz, interval '1 day');
+ANALYZE ab1;
+DROP TABLE ab1;
 -- Verify supported object types for extended statistics
 CREATE schema tststats;
 CREATE TABLE tststats.t (a int, b int, c text);
@@ -425,6 +474,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
          1 |      1
 (1 row)
 
+-- a => b, a => c, b => c
+TRUNCATE functional_dependencies;
+DROP STATISTICS func_deps_stat;
+-- now do the same thing, but with expressions
+INSERT INTO functional_dependencies (a, b, c, filler1)
+     SELECT i, i, i, i FROM generate_series(1,5000) s(i);
+ANALYZE functional_dependencies;
+SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1');
+ estimated | actual 
+-----------+--------
+         1 |     35
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1 AND mod(c, 7) = 1');
+ estimated | actual 
+-----------+--------
+         1 |      5
+(1 row)
+
+-- create statistics
+CREATE STATISTICS func_deps_stat (dependencies) ON (mod(a,11)), (mod(b::int, 13)), (mod(c, 7)) FROM functional_dependencies;
+ANALYZE functional_dependencies;
+SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1');
+ estimated | actual 
+-----------+--------
+        35 |     35
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1 AND mod(c, 7) = 1');
+ estimated | actual 
+-----------+--------
+         5 |      5
+(1 row)
+
 -- a => b, a => c, b => c
 TRUNCATE functional_dependencies;
 DROP STATISTICS func_deps_stat;
@@ -894,6 +977,39 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b =
          1 |      1
 (1 row)
 
+TRUNCATE mcv_lists;
+DROP STATISTICS mcv_lists_stats;
+-- random data (no MCV list), but with expression
+INSERT INTO mcv_lists (a, b, c, filler1)
+     SELECT i, i, i, i FROM generate_series(1,5000) s(i);
+ANALYZE mcv_lists;
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,37) = 1 AND mod(b::int,41) = 1');
+ estimated | actual 
+-----------+--------
+         1 |      4
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,37) = 1 AND mod(b::int,41) = 1 AND mod(c,47) = 1');
+ estimated | actual 
+-----------+--------
+         1 |      1
+(1 row)
+
+-- create statistics
+CREATE STATISTICS mcv_lists_stats (mcv) ON (mod(a,37)), (mod(b::int,41)), (mod(c,47)) FROM mcv_lists;
+ANALYZE mcv_lists;
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,37) = 1 AND mod(b::int,41) = 1');
+ estimated | actual 
+-----------+--------
+         3 |      4
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,37) = 1 AND mod(b::int,41) = 1 AND mod(c,47) = 1');
+ estimated | actual 
+-----------+--------
+         1 |      1
+(1 row)
+
 -- 100 distinct combinations, all in the MCV list
 TRUNCATE mcv_lists;
 DROP STATISTICS mcv_lists_stats;
@@ -1119,6 +1235,12 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = '
        200 |    200
 (1 row)
 
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
+ estimated | actual 
+-----------+--------
+       200 |    200
+(1 row)
+
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')');
  estimated | actual 
 -----------+--------
@@ -1205,6 +1327,458 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b =
         50 |     50
 (1 row)
 
+-- 100 distinct combinations, all in the MCV list, but with expressions
+TRUNCATE mcv_lists;
+DROP STATISTICS mcv_lists_stats;
+INSERT INTO mcv_lists (a, b, c, filler1)
+     SELECT i, i, i, i FROM generate_series(1,5000) s(i);
+ANALYZE mcv_lists;
+-- without any stats on the expressions, we have to use default selectivities, which
+-- is why the estimates here are different from the pre-computed case above
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,100) AND 1 = mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 1 AND mod(b::int,50) < 1');
+ estimated | actual 
+-----------+--------
+       556 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,100) AND 1 > mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+       556 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 0 AND mod(b::int,50) <= 0');
+ estimated | actual 
+-----------+--------
+       556 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= mod(a,100) AND 0 >= mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+       556 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1 AND mod(c,25) = 1');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND mod(b::int,50) < 1 AND mod(c,25) < 5');
+ estimated | actual 
+-----------+--------
+       185 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND 1 > mod(b::int,50) AND 5 > mod(c,25)');
+ estimated | actual 
+-----------+--------
+       185 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 4 AND mod(b::int,50) <= 0 AND mod(c,25) <= 4');
+ estimated | actual 
+-----------+--------
+       185 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= mod(a,100) AND 0 >= mod(b::int,50) AND 4 >= mod(c,25)');
+ estimated | actual 
+-----------+--------
+       185 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1');
+ estimated | actual 
+-----------+--------
+        75 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+ estimated | actual 
+-----------+--------
+        75 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52) AND mod(b::int,50) IN ( 1, 2)');
+ estimated | actual 
+-----------+--------
+         1 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52, NULL) AND mod(b::int,50) IN ( 1, 2, NULL)');
+ estimated | actual 
+-----------+--------
+         1 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2])');
+ estimated | actual 
+-----------+--------
+         1 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2, NULL])');
+ estimated | actual 
+-----------+--------
+         1 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, 2, 3]) AND mod(b::int,50) IN (1, 2, 3)');
+ estimated | actual 
+-----------+--------
+        53 |    150
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,50) IN (1, 2, NULL, 3)');
+ estimated | actual 
+-----------+--------
+        53 |    150
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+ estimated | actual 
+-----------+--------
+       391 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3, NULL])');
+ estimated | actual 
+-----------+--------
+       391 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, 3) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+ estimated | actual 
+-----------+--------
+         6 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, NULL, 3) AND mod(c,25) > ANY (ARRAY[1, 2, NULL, 3])');
+ estimated | actual 
+-----------+--------
+         6 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+ estimated | actual 
+-----------+--------
+        75 |    200
+(1 row)
+
+-- create statistics with expressions only (we create three separate stats, in order not to build more complex extended stats)
+CREATE STATISTICS mcv_lists_stats_1 ON (mod(a,100)) FROM mcv_lists;
+CREATE STATISTICS mcv_lists_stats_2 ON (mod(b::int,50)) FROM mcv_lists;
+CREATE STATISTICS mcv_lists_stats_3 ON (mod(c,25)) FROM mcv_lists;
+ANALYZE mcv_lists;
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,100) AND 1 = mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 1 AND mod(b::int,50) < 1');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,100) AND 1 > mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 0 AND mod(b::int,50) <= 0');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= mod(a,100) AND 0 >= mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1 AND mod(c,25) = 1');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND mod(b::int,50) < 1 AND mod(c,25) < 5');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND 1 > mod(b::int,50) AND 5 > mod(c,25)');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 4 AND mod(b::int,50) <= 0 AND mod(c,25) <= 4');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= mod(a,100) AND 0 >= mod(b::int,50) AND 4 >= mod(c,25)');
+ estimated | actual 
+-----------+--------
+         1 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1');
+ estimated | actual 
+-----------+--------
+       343 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+ estimated | actual 
+-----------+--------
+       343 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52) AND mod(b::int,50) IN ( 1, 2)');
+ estimated | actual 
+-----------+--------
+         8 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52, NULL) AND mod(b::int,50) IN ( 1, 2, NULL)');
+ estimated | actual 
+-----------+--------
+         8 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2])');
+ estimated | actual 
+-----------+--------
+         8 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2, NULL])');
+ estimated | actual 
+-----------+--------
+         8 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, 2, 3]) AND mod(b::int,50) IN (1, 2, 3)');
+ estimated | actual 
+-----------+--------
+        26 |    150
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,50) IN (1, 2, NULL, 3)');
+ estimated | actual 
+-----------+--------
+        26 |    150
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+ estimated | actual 
+-----------+--------
+        10 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3, NULL])');
+ estimated | actual 
+-----------+--------
+        10 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, 3) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+ estimated | actual 
+-----------+--------
+         1 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, NULL, 3) AND mod(c,25) > ANY (ARRAY[1, 2, NULL, 3])');
+ estimated | actual 
+-----------+--------
+         1 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+ estimated | actual 
+-----------+--------
+       343 |    200
+(1 row)
+
+DROP STATISTICS mcv_lists_stats_1;
+DROP STATISTICS mcv_lists_stats_2;
+DROP STATISTICS mcv_lists_stats_3;
+-- create statistics with both MCV and expressions
+CREATE STATISTICS mcv_lists_stats (mcv) ON (mod(a,100)), (mod(b::int,50)), (mod(c,25)) FROM mcv_lists;
+ANALYZE mcv_lists;
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,100) AND 1 = mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 1 AND mod(b::int,50) < 1');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,100) AND 1 > mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 0 AND mod(b::int,50) <= 0');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= mod(a,100) AND 0 >= mod(b::int,50)');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1 AND mod(c,25) = 1');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND mod(b::int,50) < 1 AND mod(c,25) < 5');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND 1 > mod(b::int,50) AND 5 > mod(c,25)');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 4 AND mod(b::int,50) <= 0 AND mod(c,25) <= 4');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= mod(a,100) AND 0 >= mod(b::int,50) AND 4 >= mod(c,25)');
+ estimated | actual 
+-----------+--------
+        50 |     50
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1');
+ estimated | actual 
+-----------+--------
+       200 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+ estimated | actual 
+-----------+--------
+       200 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52) AND mod(b::int,50) IN ( 1, 2)');
+ estimated | actual 
+-----------+--------
+       200 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52, NULL) AND mod(b::int,50) IN ( 1, 2, NULL)');
+ estimated | actual 
+-----------+--------
+       200 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2])');
+ estimated | actual 
+-----------+--------
+       200 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2, NULL])');
+ estimated | actual 
+-----------+--------
+       200 |    200
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, 2, 3]) AND mod(b::int,50) IN (1, 2, 3)');
+ estimated | actual 
+-----------+--------
+       150 |    150
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,50) IN (1, 2, NULL, 3)');
+ estimated | actual 
+-----------+--------
+       150 |    150
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+ estimated | actual 
+-----------+--------
+       100 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3, NULL])');
+ estimated | actual 
+-----------+--------
+       100 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, 3) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+ estimated | actual 
+-----------+--------
+       100 |    100
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, NULL, 3) AND mod(c,25) > ANY (ARRAY[1, 2, NULL, 3])');
+ estimated | actual 
+-----------+--------
+       100 |    100
+(1 row)
+
+-- we can't use the statistic for OR clauses that are not fully covered (missing 'd' attribute)
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+ estimated | actual 
+-----------+--------
+       200 |    200
+(1 row)
+
 -- 100 distinct combinations with NULL values, all in the MCV list
 TRUNCATE mcv_lists;
 DROP STATISTICS mcv_lists_stats;
@@ -1710,6 +2284,102 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 OR
 (1 row)
 
 DROP TABLE mcv_lists_multi;
+-- statistics on integer expressions
+CREATE TABLE expr_stats (a int, b int, c int);
+INSERT INTO expr_stats SELECT mod(i,10), mod(i,10), mod(i,10) FROM generate_series(1,10000) s(i);
+ANALYZE expr_stats;
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (2*a) = 0 AND (3*b) = 0');
+ estimated | actual 
+-----------+--------
+         1 |   1000
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (a+b) = 0 AND (a-b) = 0');
+ estimated | actual 
+-----------+--------
+         1 |   1000
+(1 row)
+
+CREATE STATISTICS expr_stats_1 (mcv) ON (a+b), (a-b), (2*a), (3*b) FROM expr_stats;
+ANALYZE expr_stats;
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (2*a) = 0 AND (3*b) = 0');
+ estimated | actual 
+-----------+--------
+      1000 |   1000
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (a+b) = 0 AND (a-b) = 0');
+ estimated | actual 
+-----------+--------
+      1000 |   1000
+(1 row)
+
+-- FIXME add dependency tracking for expressions, to automatically drop after DROP TABLE
+-- (not it fails, when there are no simple column references)
+DROP STATISTICS expr_stats_1;
+DROP TABLE expr_stats;
+-- statistics on a mix columns and expressions
+CREATE TABLE expr_stats (a int, b int, c int);
+INSERT INTO expr_stats SELECT mod(i,10), mod(i,10), mod(i,10) FROM generate_series(1,10000) s(i);
+ANALYZE expr_stats;
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (2*a) = 0 AND (3*b) = 0');
+ estimated | actual 
+-----------+--------
+         1 |   1000
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 3 AND b = 3 AND (a-b) = 0');
+ estimated | actual 
+-----------+--------
+         1 |   1000
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND b = 1 AND (a-b) = 0');
+ estimated | actual 
+-----------+--------
+         1 |      0
+(1 row)
+
+CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (2*a), (3*b), (a+b), (a-b) FROM expr_stats;
+ANALYZE expr_stats;
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (2*a) = 0 AND (3*b) = 0');
+ estimated | actual 
+-----------+--------
+      1000 |   1000
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 3 AND b = 3 AND (a-b) = 0');
+ estimated | actual 
+-----------+--------
+      1000 |   1000
+(1 row)
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND b = 1 AND (a-b) = 0');
+ estimated | actual 
+-----------+--------
+         1 |      0
+(1 row)
+
+DROP TABLE expr_stats;
+-- statistics on expressions with different data types
+CREATE TABLE expr_stats (a int, b name, c text);
+INSERT INTO expr_stats SELECT mod(i,10), md5(mod(i,10)::text), md5(mod(i,10)::text) FROM generate_series(1,10000) s(i);
+ANALYZE expr_stats;
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0''');
+ estimated | actual 
+-----------+--------
+       111 |   1000
+(1 row)
+
+CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (b || c), (c || b) FROM expr_stats;
+ANALYZE expr_stats;
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0''');
+ estimated | actual 
+-----------+--------
+      1000 |   1000
+(1 row)
+
+DROP TABLE expr_stats;
 -- Permission tests. Users should not be able to see specific data values in
 -- the extended statistics, if they lack permission to see those values in
 -- the underlying table.
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 06b76f949d..4929d373a2 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -124,7 +124,9 @@ CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);
 CREATE INDEX ctlt1_b_key ON ctlt1 (b);
 CREATE INDEX ctlt1_fnidx ON ctlt1 ((a || b));
 CREATE STATISTICS ctlt1_a_b_stat ON a,b FROM ctlt1;
+CREATE STATISTICS ctlt1_expr_stat ON (a || b) FROM ctlt1;
 COMMENT ON STATISTICS ctlt1_a_b_stat IS 'ab stats';
+COMMENT ON STATISTICS ctlt1_expr_stat IS 'ab expr stats';
 COMMENT ON COLUMN ctlt1.a IS 'A';
 COMMENT ON COLUMN ctlt1.b IS 'B';
 COMMENT ON CONSTRAINT ctlt1_a_check ON ctlt1 IS 't1_a_check';
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 7912e733ae..b3d279a0e9 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -33,10 +33,16 @@ CREATE STATISTICS tst ON a, b;
 CREATE STATISTICS tst FROM sometab;
 CREATE STATISTICS tst ON a, b FROM nonexistent;
 CREATE STATISTICS tst ON a, b FROM pg_class;
+CREATE STATISTICS tst ON relname FROM pg_class;
 CREATE STATISTICS tst ON relname, relname, relnatts FROM pg_class;
-CREATE STATISTICS tst ON relnatts + relpages FROM pg_class;
-CREATE STATISTICS tst ON (relpages, reltuples) FROM pg_class;
+CREATE STATISTICS tst ON relname, relname, relnatts, relname, relname, relnatts, relname, relname, relnatts FROM pg_class;
+CREATE STATISTICS tst ON relname, relname, relnatts, relname, relname, (relname || 'x'), (relnatts + 1), (relname || 'x'), (relname || 'x'), (relnatts + 1) FROM pg_class;
+CREATE STATISTICS tst ON (relname || 'x'), (relname || 'x'), (relnatts + 1), (relname || 'x'), (relname || 'x'), (relnatts + 1), (relname || 'x'), (relname || 'x'), (relnatts + 1) FROM pg_class;
+CREATE STATISTICS tst ON (relname || 'x'), (relname || 'x'), relnatts FROM pg_class;
 CREATE STATISTICS tst (unrecognized) ON relname, relnatts FROM pg_class;
+-- incorrect expressions
+CREATE STATISTICS tst ON relnatts + relpages FROM pg_class; -- missing parentheses
+CREATE STATISTICS tst ON (relpages, reltuples) FROM pg_class; -- tuple expression
 
 -- Ensure stats are dropped sanely, and test IF NOT EXISTS while at it
 CREATE TABLE ab1 (a INTEGER, b INTEGER, c INTEGER);
@@ -95,6 +101,36 @@ CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1;
 ANALYZE ab1;
 DROP TABLE ab1 CASCADE;
 
+-- basic test for statistics on expressions
+CREATE TABLE ab1 (a INTEGER, b INTEGER, c TIMESTAMP, d TIMESTAMPTZ);
+
+-- expression stats may be built on a single expression column
+CREATE STATISTICS ab1_exprstat_1 ON (a+b) FROM ab1;
+
+-- with a single expression, we only enable expression statistics
+CREATE STATISTICS ab1_exprstat_2 ON (a+b) FROM ab1;
+SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_2';
+
+-- adding anything to the expression builds all statistics kinds
+CREATE STATISTICS ab1_exprstat_3 ON (a+b), a FROM ab1;
+SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_3';
+
+-- expression must be immutable, but date_trunc on timestamptz is not
+CREATE STATISTICS ab1_exprstat_4 ON date_trunc('day', d) FROM ab1;
+
+-- but on timestamp it should work fine
+CREATE STATISTICS ab1_exprstat_5 ON date_trunc('day', c) FROM ab1;
+
+-- insert some data and run analyze, to test that these cases build properly
+INSERT INTO ab1
+SELECT
+    generate_series(1,10),
+    generate_series(1,10),
+    generate_series('2020-10-01'::timestamp, '2020-10-10'::timestamp, interval '1 day'),
+    generate_series('2020-10-01'::timestamptz, '2020-10-10'::timestamptz, interval '1 day');
+ANALYZE ab1;
+DROP TABLE ab1;
+
 -- Verify supported object types for extended statistics
 CREATE schema tststats;
 
@@ -270,6 +306,29 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
 TRUNCATE functional_dependencies;
 DROP STATISTICS func_deps_stat;
 
+-- now do the same thing, but with expressions
+INSERT INTO functional_dependencies (a, b, c, filler1)
+     SELECT i, i, i, i FROM generate_series(1,5000) s(i);
+
+ANALYZE functional_dependencies;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1 AND mod(c, 7) = 1');
+
+-- create statistics
+CREATE STATISTICS func_deps_stat (dependencies) ON (mod(a,11)), (mod(b::int, 13)), (mod(c, 7)) FROM functional_dependencies;
+
+ANALYZE functional_dependencies;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1 AND mod(c, 7) = 1');
+
+-- a => b, a => c, b => c
+TRUNCATE functional_dependencies;
+DROP STATISTICS func_deps_stat;
+
 INSERT INTO functional_dependencies (a, b, c, filler1)
      SELECT mod(i,100), mod(i,50), mod(i,25), i FROM generate_series(1,5000) s(i);
 
@@ -477,6 +536,28 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b =
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1');
 
+TRUNCATE mcv_lists;
+DROP STATISTICS mcv_lists_stats;
+
+-- random data (no MCV list), but with expression
+INSERT INTO mcv_lists (a, b, c, filler1)
+     SELECT i, i, i, i FROM generate_series(1,5000) s(i);
+
+ANALYZE mcv_lists;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,37) = 1 AND mod(b::int,41) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,37) = 1 AND mod(b::int,41) = 1 AND mod(c,47) = 1');
+
+-- create statistics
+CREATE STATISTICS mcv_lists_stats (mcv) ON (mod(a,37)), (mod(b::int,41)), (mod(c,47)) FROM mcv_lists;
+
+ANALYZE mcv_lists;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,37) = 1 AND mod(b::int,41) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,37) = 1 AND mod(b::int,41) = 1 AND mod(c,47) = 1');
+
 -- 100 distinct combinations, all in the MCV list
 TRUNCATE mcv_lists;
 DROP STATISTICS mcv_lists_stats;
@@ -563,6 +644,8 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = '
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
 
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
+
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')');
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52, NULL) AND b IN ( ''1'', ''2'', NULL)');
@@ -600,6 +683,180 @@ ANALYZE mcv_lists;
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1''');
 
+
+-- 100 distinct combinations, all in the MCV list, but with expressions
+TRUNCATE mcv_lists;
+DROP STATISTICS mcv_lists_stats;
+
+INSERT INTO mcv_lists (a, b, c, filler1)
+     SELECT i, i, i, i FROM generate_series(1,5000) s(i);
+
+ANALYZE mcv_lists;
+
+-- without any stats on the expressions, we have to use default selectivities, which
+-- is why the estimates here are different from the pre-computed case above
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,100) AND 1 = mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 1 AND mod(b::int,50) < 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,100) AND 1 > mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 0 AND mod(b::int,50) <= 0');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= mod(a,100) AND 0 >= mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1 AND mod(c,25) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND mod(b::int,50) < 1 AND mod(c,25) < 5');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND 1 > mod(b::int,50) AND 5 > mod(c,25)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 4 AND mod(b::int,50) <= 0 AND mod(c,25) <= 4');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= mod(a,100) AND 0 >= mod(b::int,50) AND 4 >= mod(c,25)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52) AND mod(b::int,50) IN ( 1, 2)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52, NULL) AND mod(b::int,50) IN ( 1, 2, NULL)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2, NULL])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, 2, 3]) AND mod(b::int,50) IN (1, 2, 3)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,50) IN (1, 2, NULL, 3)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3, NULL])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, 3) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, NULL, 3) AND mod(c,25) > ANY (ARRAY[1, 2, NULL, 3])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+
+-- create statistics with expressions only (we create three separate stats, in order not to build more complex extended stats)
+CREATE STATISTICS mcv_lists_stats_1 ON (mod(a,100)) FROM mcv_lists;
+CREATE STATISTICS mcv_lists_stats_2 ON (mod(b::int,50)) FROM mcv_lists;
+CREATE STATISTICS mcv_lists_stats_3 ON (mod(c,25)) FROM mcv_lists;
+
+ANALYZE mcv_lists;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,100) AND 1 = mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 1 AND mod(b::int,50) < 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,100) AND 1 > mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 0 AND mod(b::int,50) <= 0');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= mod(a,100) AND 0 >= mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1 AND mod(c,25) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND mod(b::int,50) < 1 AND mod(c,25) < 5');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND 1 > mod(b::int,50) AND 5 > mod(c,25)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 4 AND mod(b::int,50) <= 0 AND mod(c,25) <= 4');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= mod(a,100) AND 0 >= mod(b::int,50) AND 4 >= mod(c,25)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52) AND mod(b::int,50) IN ( 1, 2)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52, NULL) AND mod(b::int,50) IN ( 1, 2, NULL)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2, NULL])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, 2, 3]) AND mod(b::int,50) IN (1, 2, 3)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,50) IN (1, 2, NULL, 3)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3, NULL])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, 3) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, NULL, 3) AND mod(c,25) > ANY (ARRAY[1, 2, NULL, 3])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+
+DROP STATISTICS mcv_lists_stats_1;
+DROP STATISTICS mcv_lists_stats_2;
+DROP STATISTICS mcv_lists_stats_3;
+
+-- create statistics with both MCV and expressions
+CREATE STATISTICS mcv_lists_stats (mcv) ON (mod(a,100)), (mod(b::int,50)), (mod(c,25)) FROM mcv_lists;
+
+ANALYZE mcv_lists;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,100) AND 1 = mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 1 AND mod(b::int,50) < 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,100) AND 1 > mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 0 AND mod(b::int,50) <= 0');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= mod(a,100) AND 0 >= mod(b::int,50)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 AND mod(b::int,50) = 1 AND mod(c,25) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND mod(b::int,50) < 1 AND mod(c,25) < 5');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < 5 AND 1 > mod(b::int,50) AND 5 > mod(c,25)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= 4 AND mod(b::int,50) <= 0 AND mod(c,25) <= 4');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= mod(a,100) AND 0 >= mod(b::int,50) AND 4 >= mod(c,25)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52) AND mod(b::int,50) IN ( 1, 2)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) IN (1, 2, 51, 52, NULL) AND mod(b::int,50) IN ( 1, 2, NULL)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND mod(b::int,50) = ANY (ARRAY[1, 2, NULL])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, 2, 3]) AND mod(b::int,50) IN (1, 2, 3)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,50) IN (1, 2, NULL, 3)');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(c,25) > ANY (ARRAY[1, 2, 3, NULL])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, 3) AND mod(c,25) > ANY (ARRAY[1, 2, 3])');
+
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) < ALL (ARRAY[4, 5]) AND mod(b::int,50) IN (1, 2, NULL, 3) AND mod(c,25) > ANY (ARRAY[1, 2, NULL, 3])');
+
+-- we can't use the statistic for OR clauses that are not fully covered (missing 'd' attribute)
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,100) = 1 OR mod(b::int,50) = 1 OR mod(c,25) = 1 OR d IS NOT NULL');
+
 -- 100 distinct combinations with NULL values, all in the MCV list
 TRUNCATE mcv_lists;
 DROP STATISTICS mcv_lists_stats;
@@ -892,6 +1149,59 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 OR
 
 DROP TABLE mcv_lists_multi;
 
+
+-- statistics on integer expressions
+CREATE TABLE expr_stats (a int, b int, c int);
+INSERT INTO expr_stats SELECT mod(i,10), mod(i,10), mod(i,10) FROM generate_series(1,10000) s(i);
+ANALYZE expr_stats;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (2*a) = 0 AND (3*b) = 0');
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (a+b) = 0 AND (a-b) = 0');
+
+CREATE STATISTICS expr_stats_1 (mcv) ON (a+b), (a-b), (2*a), (3*b) FROM expr_stats;
+ANALYZE expr_stats;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (2*a) = 0 AND (3*b) = 0');
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (a+b) = 0 AND (a-b) = 0');
+
+-- FIXME add dependency tracking for expressions, to automatically drop after DROP TABLE
+-- (not it fails, when there are no simple column references)
+DROP STATISTICS expr_stats_1;
+DROP TABLE expr_stats;
+
+-- statistics on a mix columns and expressions
+CREATE TABLE expr_stats (a int, b int, c int);
+INSERT INTO expr_stats SELECT mod(i,10), mod(i,10), mod(i,10) FROM generate_series(1,10000) s(i);
+ANALYZE expr_stats;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (2*a) = 0 AND (3*b) = 0');
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 3 AND b = 3 AND (a-b) = 0');
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND b = 1 AND (a-b) = 0');
+
+CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (2*a), (3*b), (a+b), (a-b) FROM expr_stats;
+ANALYZE expr_stats;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (2*a) = 0 AND (3*b) = 0');
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 3 AND b = 3 AND (a-b) = 0');
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND b = 1 AND (a-b) = 0');
+
+DROP TABLE expr_stats;
+
+-- statistics on expressions with different data types
+CREATE TABLE expr_stats (a int, b name, c text);
+INSERT INTO expr_stats SELECT mod(i,10), md5(mod(i,10)::text), md5(mod(i,10)::text) FROM generate_series(1,10000) s(i);
+ANALYZE expr_stats;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0''');
+
+CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (b || c), (c || b) FROM expr_stats;
+ANALYZE expr_stats;
+
+SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0''');
+
+DROP TABLE expr_stats;
+
+
 -- Permission tests. Users should not be able to see specific data values in
 -- the extended statistics, if they lack permission to see those values in
 -- the underlying table.
-- 
2.26.2


--------------76B8D0DC8AE327D516738282--





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
@ 2024-01-22 06:09 Peter Smith <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Peter Smith @ 2024-01-22 06:09 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Ashutosh Bapat <[email protected]>; [email protected]

2024-01 Commitfest.

Hi, This patch has a CF status of "Needs Review" [1], but it seems
there were CFbot test failures last time it was run [2]. Please have a
look and post an updated version if necessary.

======
[1] https://commitfest.postgresql.org/46/4538/
[2] https://cirrus-ci.com/github/postgresql-cfbot/postgresql/commitfest/46/4538

Kind Regards,
Peter Smith.





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
@ 2025-03-07 00:58 ` Jeff Davis <[email protected]>
  2025-03-07 01:47   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2 siblings, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-07 00:58 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Thu, 2025-03-06 at 08:49 -0500, Corey Huinker wrote:
> Unless some check was being done by the 'foo.bar'::regclass cast, I
> understand why we should add one.

"For schemas, allows access to objects contained in the schema
(assuming that the objects' own privilege requirements are also met).
Essentially this allows the grantee to “look up” objects within the
schema. Without this permission, it is still possible to see the object
names, e.g., by querying system catalogs. Also, after revoking this
permission, existing sessions might have statements that have
previously performed this lookup, so this is not a completely secure
way to prevent object access."

https://www.postgresql.org/docs/current/ddl-priv.html

The above text indicates that we should do the check, but also that
it's not terribly important for actual security.

> If we do, we'll want to change downgrade the following errors to
> warn+return false:

Perhaps we should consider the schemaname/relname change as one patch,
which maintains relation lookup failures as hard ERRORs, and a
"downgrade errors to warnings" as a separate patch.

> I agree, but the thread conversation had already shifted to doing
> just one single call to pg_stats, so this was just a demonstration.

It's a simple patch and the discussion seems to be shifting toward
parallelism[1] rather than batching[2]. In that case it still seems
like a good change to me, so I'm inclined to commit it after I verify
that it improves performance.

Regards,
	Jeff Davis

[1] 
https://www.postgresql.org/message-id/[email protected]

[2] https://www.postgresql.org/message-id/[email protected]





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 00:58 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-07 01:47   ` Corey Huinker <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Corey Huinker @ 2025-03-07 01:47 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
>
> https://www.postgresql.org/docs/current/ddl-priv.html
>
> The above text indicates that we should do the check, but also that
> it's not terribly important for actual security.
>

Ok, I'm convinced.



>
> > If we do, we'll want to change downgrade the following errors to
> > warn+return false:
>
> Perhaps we should consider the schemaname/relname change as one patch,
> which maintains relation lookup failures as hard ERRORs, and a
> "downgrade errors to warnings" as a separate patch.
>

+1


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
@ 2025-03-07 01:42 ` Jeff Davis <[email protected]>
  2025-03-07 02:56   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-07 16:22   ` Re: Statistics Import and Export Andres Freund <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2 siblings, 3 replies; 87+ messages in thread

From: Jeff Davis @ 2025-03-07 01:42 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; Andres Freund <[email protected]>; +Cc: Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Thu, 2025-03-06 at 11:15 -0500, Robert Haas wrote:
> To be honest, I am a bit surprised that we decided to enable this by
> default. It's not obvious to me that statistics should be regarded as
> part of the database in the same way that table definitions or table
> data are. That said, I'm not overwhelmingly opposed to that choice.
> However, even if it's the right choice in theory, we should maybe
> rethink if it's going to be too slow or use too much memory.

I don't have a strong opinion about whether stats will be opt-out or
opt-in for v18, but if they are opt-in, we would need to adjust the
available options a bit.

At minimum, we would need to at least add the option "--with-
statistics", because right now the only way to explicitly request stats
is to say "--statistics-only".

To generalize this concept: for each of {schema, data, stats} users
might want "yes", "no", or "only".

If we use this options scheme, it would be easy to change the default
for stats independently of the other options, if necessary, without
surprising consequences.

Patch attached. This patch does NOT change the default; stats are still
opt-out. But it makes it easier for users to start specifying what they
want or not explicitly, or to rely on the defaults if they prefer.

Note that the patch would mean we go from 2 options in v17:
  --{schema|data}-only

to 9 options in v18:
  --{with|no}-{schema|data|stats} and
  --{schema|data|stats}-only

I suggest we adjust the options now with something resembling the
attached patch and decide on changing the default sometime during beta.

Regards,
	Jeff Davis



Attachments:

  [text/x-patch] v1-0001-Add-pg_dump-with-X-options.patch (13.0K, ../../[email protected]/2-v1-0001-Add-pg_dump-with-X-options.patch)
  download | inline diff:
From c47fc9e570ddd083097f4bfc708465cf644f48c2 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 6 Mar 2025 17:35:41 -0800
Subject: [PATCH v1] Add pg_dump --with-X options.

---
 doc/src/sgml/ref/pg_dump.sgml    | 27 +++++++++++++++++++
 doc/src/sgml/ref/pg_dumpall.sgml | 27 +++++++++++++++++++
 doc/src/sgml/ref/pg_restore.sgml | 27 +++++++++++++++++++
 src/bin/pg_dump/pg_dump.c        | 46 +++++++++++++++++++++++++++++---
 src/bin/pg_dump/pg_dumpall.c     | 12 +++++++++
 src/bin/pg_dump/pg_restore.c     | 44 +++++++++++++++++++++++++-----
 6 files changed, 173 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 1975054d7bf..9eba285687e 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1223,6 +1223,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--with-data</option></term>
+      <listitem>
+       <para>
+        Dump data. This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--with-schema</option></term>
+      <listitem>
+       <para>
+        Dump schema (data definitions). This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--with-statistics</option></term>
+      <listitem>
+       <para>
+        Dump statistics. This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--on-conflict-do-nothing</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index c2fa5be9519..45f127f0dc9 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -551,6 +551,33 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--with-data</option></term>
+      <listitem>
+       <para>
+        Dump data. This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--with-schema</option></term>
+      <listitem>
+       <para>
+        Dump schema (data definitions). This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--with-statistics</option></term>
+      <listitem>
+       <para>
+        Dump statistics. This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--no-unlogged-table-data</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 199ea3345f3..51e6411c8fe 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -795,6 +795,33 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--with-data</option></term>
+      <listitem>
+       <para>
+        Dump data. This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--with-schema</option></term>
+      <listitem>
+       <para>
+        Dump schema (data definitions). This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--with-statistics</option></term>
+      <listitem>
+       <para>
+        Dump statistics. This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
        <term><option>--section=<replaceable class="parameter">sectionname</replaceable></option></term>
        <listitem>
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4f4ad2ee150..31c4ac1ee57 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -433,6 +433,9 @@ main(int argc, char **argv)
 	bool		data_only = false;
 	bool		schema_only = false;
 	bool		statistics_only = false;
+	bool		with_data = false;
+	bool		with_schema = false;
+	bool		with_statistics = false;
 	bool		no_data = false;
 	bool		no_schema = false;
 	bool		no_statistics = false;
@@ -508,6 +511,9 @@ main(int argc, char **argv)
 		{"no-toast-compression", no_argument, &dopt.no_toast_compression, 1},
 		{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
 		{"no-sync", no_argument, NULL, 7},
+		{"with-data", no_argument, NULL, 22},
+		{"with-schema", no_argument, NULL, 23},
+		{"with-statistics", no_argument, NULL, 24},
 		{"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
 		{"rows-per-insert", required_argument, NULL, 10},
 		{"include-foreign-data", required_argument, NULL, 11},
@@ -776,6 +782,18 @@ main(int argc, char **argv)
 				no_statistics = true;
 				break;
 
+			case 22:
+				with_data = true;
+				break;
+
+			case 23:
+				with_schema = true;
+				break;
+
+			case 24:
+				with_statistics = true;
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -811,6 +829,7 @@ main(int argc, char **argv)
 	if (dopt.binary_upgrade)
 		dopt.sequence_data = 1;
 
+	/* reject conflicting "-only" options */
 	if (data_only && schema_only)
 		pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
 	if (schema_only && statistics_only)
@@ -818,6 +837,7 @@ main(int argc, char **argv)
 	if (data_only && statistics_only)
 		pg_fatal("options -a/--data-only and --statistics-only cannot be used together");
 
+	/* reject conflicting "-only" and "no-" options */
 	if (data_only && no_data)
 		pg_fatal("options -a/--data-only and --no-data cannot be used together");
 	if (schema_only && no_schema)
@@ -825,6 +845,14 @@ main(int argc, char **argv)
 	if (statistics_only && no_statistics)
 		pg_fatal("options --statistics-only and --no-statistics cannot be used together");
 
+	/* reject conflicting "with-" and "no-" options */
+	if (with_data && no_data)
+		pg_fatal("options --with-data and --no-data cannot be used together");
+	if (with_schema && no_schema)
+		pg_fatal("options --with-schema and --no-schema cannot be used together");
+	if (with_statistics && no_statistics)
+		pg_fatal("options --with-statistics and --no-statistics cannot be used together");
+
 	if (schema_only && foreign_servers_include_patterns.head != NULL)
 		pg_fatal("options -s/--schema-only and --include-foreign-data cannot be used together");
 
@@ -837,10 +865,20 @@ main(int argc, char **argv)
 	if (dopt.if_exists && !dopt.outputClean)
 		pg_fatal("option --if-exists requires option -c/--clean");
 
-	/* set derivative flags */
-	dopt.dumpData = data_only || (!schema_only && !statistics_only && !no_data);
-	dopt.dumpSchema = schema_only || (!data_only && !statistics_only && !no_schema);
-	dopt.dumpStatistics = statistics_only || (!data_only && !schema_only && !no_statistics);
+	/*
+	 * Set derivative flags. An "-only" option may be overridden by an
+	 * explicit "with-" option; e.g. "--schema-only --with-statistics" will
+	 * include schema and statistics. Other ambiguous or nonsensical
+	 * combinations, e.g. "--schema-only --no-schema", will have already
+	 * caused an error in one of the checks above.
+	 */
+	dopt.dumpData = ((dopt.dumpData && !schema_only && !statistics_only) ||
+					 (data_only || with_data)) && !no_data;
+	dopt.dumpSchema = ((dopt.dumpSchema && !data_only && !statistics_only) ||
+					   (schema_only || with_schema)) && !no_schema;
+	dopt.dumpStatistics = ((dopt.dumpStatistics && !schema_only && !data_only) ||
+						   (statistics_only || with_statistics)) && !no_statistics;
+
 
 	/*
 	 * --inserts are already implied above if --column-inserts or
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index e0867242526..a7e8c0d2ad5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -110,6 +110,9 @@ static int	no_subscriptions = 0;
 static int	no_toast_compression = 0;
 static int	no_unlogged_table_data = 0;
 static int	no_role_passwords = 0;
+static int	with_data = 0;
+static int	with_schema = 0;
+static int	with_statistics = 0;
 static int	server_version;
 static int	load_via_partition_root = 0;
 static int	on_conflict_do_nothing = 0;
@@ -182,6 +185,9 @@ main(int argc, char *argv[])
 		{"no-sync", no_argument, NULL, 4},
 		{"no-toast-compression", no_argument, &no_toast_compression, 1},
 		{"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
+		{"with-data", no_argument, &with_data, 1},
+		{"with-schema", no_argument, &with_schema, 1},
+		{"with-statistics", no_argument, &with_statistics, 1},
 		{"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1},
 		{"rows-per-insert", required_argument, NULL, 7},
 		{"statistics-only", no_argument, &statistics_only, 1},
@@ -471,6 +477,12 @@ main(int argc, char *argv[])
 		appendPQExpBufferStr(pgdumpopts, " --no-toast-compression");
 	if (no_unlogged_table_data)
 		appendPQExpBufferStr(pgdumpopts, " --no-unlogged-table-data");
+	if (with_data)
+		appendPQExpBufferStr(pgdumpopts, " --with-data");
+	if (with_schema)
+		appendPQExpBufferStr(pgdumpopts, " --with-schema");
+	if (with_statistics)
+		appendPQExpBufferStr(pgdumpopts, " --with-statistics");
 	if (on_conflict_do_nothing)
 		appendPQExpBufferStr(pgdumpopts, " --on-conflict-do-nothing");
 	if (statistics_only)
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 13e4dc507e0..f22046127b7 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -81,6 +81,9 @@ main(int argc, char **argv)
 	static int	no_subscriptions = 0;
 	static int	strict_names = 0;
 	static int	statistics_only = 0;
+	static int	with_data = 0;
+	static int	with_schema = 0;
+	static int	with_statistics = 0;
 
 	struct option cmdopts[] = {
 		{"clean", 0, NULL, 'c'},
@@ -134,6 +137,9 @@ main(int argc, char **argv)
 		{"no-security-labels", no_argument, &no_security_labels, 1},
 		{"no-subscriptions", no_argument, &no_subscriptions, 1},
 		{"no-statistics", no_argument, &no_statistics, 1},
+		{"with-data", no_argument, &with_data, 1},
+		{"with-schema", no_argument, &with_schema, 1},
+		{"with-statistics", no_argument, &with_statistics, 1},
 		{"statistics-only", no_argument, &statistics_only, 1},
 		{"filter", required_argument, NULL, 4},
 
@@ -349,12 +355,29 @@ main(int argc, char **argv)
 		opts->useDB = 1;
 	}
 
+	/* reject conflicting "-only" options */
 	if (data_only && schema_only)
 		pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
-	if (data_only && statistics_only)
-		pg_fatal("options -a/--data-only and --statistics-only cannot be used together");
 	if (schema_only && statistics_only)
 		pg_fatal("options -s/--schema-only and --statistics-only cannot be used together");
+	if (data_only && statistics_only)
+		pg_fatal("options -a/--data-only and --statistics-only cannot be used together");
+
+	/* reject conflicting "-only" and "no-" options */
+	if (data_only && no_data)
+		pg_fatal("options -a/--data-only and --no-data cannot be used together");
+	if (schema_only && no_schema)
+		pg_fatal("options -s/--schema-only and --no-schema cannot be used together");
+	if (statistics_only && no_statistics)
+		pg_fatal("options --statistics-only and --no-statistics cannot be used together");
+
+	/* reject conflicting "with-" and "no-" options */
+	if (with_data && no_data)
+		pg_fatal("options --with-data and --no-data cannot be used together");
+	if (with_schema && no_schema)
+		pg_fatal("options --with-schema and --no-schema cannot be used together");
+	if (with_statistics && no_statistics)
+		pg_fatal("options --with-statistics and --no-statistics cannot be used together");
 
 	if (data_only && opts->dropSchema)
 		pg_fatal("options -c/--clean and -a/--data-only cannot be used together");
@@ -373,10 +396,19 @@ main(int argc, char **argv)
 	if (opts->single_txn && numWorkers > 1)
 		pg_fatal("cannot specify both --single-transaction and multiple jobs");
 
-	/* set derivative flags */
-	opts->dumpData = data_only || (!no_data && !schema_only && !statistics_only);
-	opts->dumpSchema = schema_only || (!no_schema && !data_only && !statistics_only);
-	opts->dumpStatistics = statistics_only || (!no_statistics && !data_only && !schema_only);
+	/*
+	 * Set derivative flags. An "-only" option may be overridden by an
+	 * explicit "with-" option; e.g. "--schema-only --with-statistics" will
+	 * include schema and statistics. Other ambiguous or nonsensical
+	 * combinations, e.g. "--schema-only --no-schema", will have already
+	 * caused an error in one of the checks above.
+	 */
+	opts->dumpData = ((opts->dumpData && !schema_only && !statistics_only) ||
+					  (data_only || with_data)) && !no_data;
+	opts->dumpSchema = ((opts->dumpSchema && !data_only && !statistics_only) ||
+						(schema_only || with_schema)) && !no_schema;
+	opts->dumpStatistics = ((opts->dumpStatistics && !schema_only && !data_only) ||
+							(statistics_only || with_statistics)) && !no_statistics;
 
 	opts->disable_triggers = disable_triggers;
 	opts->enable_row_security = enable_row_security;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-07 02:56   ` Corey Huinker <[email protected]>
  2 siblings, 0 replies; 87+ messages in thread

From: Corey Huinker @ 2025-03-07 02:56 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
>
> Patch attached. This patch does NOT change the default; stats are still
> opt-out. But it makes it easier for users to start specifying what they
> want or not explicitly, or to rely on the defaults if they prefer.
>
> Note that the patch would mean we go from 2 options in v17:
>   --{schema|data}-only
>
> to 9 options in v18:
>   --{with|no}-{schema|data|stats} and
>   --{schema|data|stats}-only
>
> I suggest we adjust the options now with something resembling the
> attached patch and decide on changing the default sometime during beta.
>

Patch is straightforward. Comments are very clear as are docs. I can't see
anything that needs to be changed.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-07 16:22   ` Andres Freund <[email protected]>
  2025-03-07 16:53     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2 siblings, 1 reply; 87+ messages in thread

From: Andres Freund @ 2025-03-07 16:22 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Hi,

On 2025-03-06 17:42:30 -0800, Jeff Davis wrote:
> At minimum, we would need to at least add the option "--with-
> statistics", because right now the only way to explicitly request stats
> is to say "--statistics-only".

+1, this has been annoying me while testing.

I did get confused for a while because I used --statistics, as the opposite of
--no-statistics, while going back and forth between the two. Kinda appears to
work, but actually means --statistics-only, something rather different...


> To generalize this concept: for each of {schema, data, stats} users
> might want "yes", "no", or "only".

> If we use this options scheme, it would be easy to change the default
> for stats independently of the other options, if necessary, without
> surprising consequences.
> 
> Patch attached. This patch does NOT change the default; stats are still
> opt-out. But it makes it easier for users to start specifying what they
> want or not explicitly, or to rely on the defaults if they prefer.
> 
> Note that the patch would mean we go from 2 options in v17:
>   --{schema|data}-only
> 
> to 9 options in v18:
>   --{with|no}-{schema|data|stats} and
>   --{schema|data|stats}-only

Could we, instead of having --with-$foo, just use --$foo?

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 16:22   ` Re: Statistics Import and Export Andres Freund <[email protected]>
@ 2025-03-07 16:53     ` Jeff Davis <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Jeff Davis @ 2025-03-07 16:53 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Robert Haas <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, 2025-03-07 at 11:22 -0500, Andres Freund wrote:
> +1, this has been annoying me while testing.

IIRC, originally someone had questioned the need for options that
expressed what was already the default, but I can't find it right now.
Regardless, now the need is clear enough.

> Could we, instead of having --with-$foo, just use --$foo?

That creates a conflict with the existing --schema option, which is a
namespace filter.

Another idea: we could use --definitions/--data/--statistics.

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-07 17:41   ` Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2 siblings, 1 reply; 87+ messages in thread

From: Robert Treat @ 2025-03-07 17:41 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Thu, Mar 6, 2025 at 8:42 PM Jeff Davis <[email protected]> wrote:
> On Thu, 2025-03-06 at 11:15 -0500, Robert Haas wrote:
> Patch attached. This patch does NOT change the default; stats are still
> opt-out. But it makes it easier for users to start specifying what they
> want or not explicitly, or to rely on the defaults if they prefer.
>
> Note that the patch would mean we go from 2 options in v17:
>   --{schema|data}-only
>
> to 9 options in v18:
>   --{with|no}-{schema|data|stats} and
>   --{schema|data|stats}-only
>

Ugh... this feels like a bit of the combinatorial explosion,
especially if we ever need to add another option. I wonder if it would
be possible to do something simple like just providing
"--include={schema|data|stats}" where you specify only what you want,
and leave out what you don't. At the risk of not providing as many
typing shortcuts, if the logic is simpler and more extensible for
future options...


Robert Treat
https://xzilla.net





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
@ 2025-03-07 18:41     ` Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-07 18:41 UTC (permalink / raw)
  To: Robert Treat <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, 2025-03-07 at 12:41 -0500, Robert Treat wrote:
> Ugh... this feels like a bit of the combinatorial explosion,
> especially if we ever need to add another option.

Not quite that bad, because ideally the yes/no/only  would not be
expanding as well. But I agree that it feels like a lot of options.

> I wonder if it would
> be possible to do something simple like just providing
> "--include={schema|data|stats}" where you specify only what you want,
> and leave out what you don't.

Can you explain the idea in a bit more detail? Does --
include=statistics mean include statistics also or statistics only? Can
you explicitly request that data be included but rely on the default
for statistics? What options would it override or conflict with?

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-07 20:46       ` Robert Treat <[email protected]>
  2025-03-07 21:43         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Robert Treat @ 2025-03-07 20:46 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, Mar 7, 2025 at 1:41 PM Jeff Davis <[email protected]> wrote:
>
> On Fri, 2025-03-07 at 12:41 -0500, Robert Treat wrote:
> > Ugh... this feels like a bit of the combinatorial explosion,
> > especially if we ever need to add another option.
>
> Not quite that bad, because ideally the yes/no/only  would not be
> expanding as well. But I agree that it feels like a lot of options.
>
> > I wonder if it would
> > be possible to do something simple like just providing
> > "--include={schema|data|stats}" where you specify only what you want,
> > and leave out what you don't.
>
> Can you explain the idea in a bit more detail? Does --
> include=statistics mean include statistics also or statistics only? Can
> you explicitly request that data be included but rely on the default
> for statistics? What options would it override or conflict with?
>

There might be some variability depending on the default behavior, but
if we assume that default means "output everything" (which is the v17
behavior), then use of --include would mean to only include items that
are listed, so:

if you want everything --include=schema,data,statistics (presumably
redundant with the default behavior)
if you want schema only --include=schema
if you want "everything except schema" --include=data,statistics

So it's pretty easy to extrapolate data only or statistics only, and
pretty easy to work up any combo of 2 of the 3.

And if someday, for example, there is ever agreement on including role
information with normal pg_dump, you add "roles" as an option to be
parsed via --include without having to create any new flags.


Robert Treat
https://xzilla.net





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
@ 2025-03-07 21:43         ` Jeff Davis <[email protected]>
  2025-03-08 03:43           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-07 21:43 UTC (permalink / raw)
  To: Robert Treat <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Corey Huinker <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, 2025-03-07 at 15:46 -0500, Robert Treat wrote:
> There might be some variability depending on the default behavior,
> but
> if we assume that default means "output everything"

The reason I posted this patch is that, depending on performance
characteristics in v18 and a decision to be made during beta, the
default may not output statistics.

So we need whatever set of options we choose to have the freedom to
change statistics to be either opt-in or opt-out, without needing to
reconsider the overall set of options.

I tried to generalize that requirement to all of
{schema|data|statistics} for consistency, but that resulted in 9
options.

We don't need the options to be perfectly consistent at the expense of
usability, though, so if 9 options is too many we can just have three
new options for stats, for a total of 5 options:

   --data-only
   --schema-only
   --statistics-only
   --statistics (stats also, regardless of default)
   --no-statistics (no stats, regardless of default)

which would allow combinations like "--schema-only --statistics" to
mean "schema and statistics but not data". There would be a bit of
weirdness because --statistics can combine with --data-only and --
schema-only, but nothing can combine with --statistics-only.

> if you want everything --include=schema,data,statistics (presumably
> redundant with the default behavior)
> if you want schema only --include=schema
> if you want "everything except schema" --include=data,statistics

That could work. Comparing to the options above yields:

   --include=statistics <=> --statistics-only
   --include=schema,data,statistics <=> --statistics
   --include=schema,statistics <=> --schema-only --statistics
   --include=data,statistics <=> --data-only --statistics
   --include=schema,data <=> --no-statistics

Not sure which approach is better.

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 21:43         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-08 03:43           ` Corey Huinker <[email protected]>
  2025-03-08 05:51             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-08 03:43 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> I tried to generalize that requirement to all of
> {schema|data|statistics} for consistency, but that resulted in 9
> options.
>

9 options that resolve to 3 boolean variables. It's not that hard.

And if we add a fourth option set, then we have 12 options. So it's O(3N),
not O(N^2).

People have scripts now that rely on the existing -only flags, and nearly
every other potentially optional thing has a -no flag. Let's leverage that.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 21:43         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-08 03:43           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-08 05:51             ` Hari Krishna Sunder <[email protected]>
  2025-03-08 07:51               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Hari Krishna Sunder @ 2025-03-08 05:51 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

To improve the performance of pg_dump can we add a new sql function that
can operate more efficiently than the pg_stats view? It could also take in
an optional list of oids to filter on.
This will help speed up the dump and restore within pg18 and future
upgrades to higher pg versions.

Thanks
Hari Krishna Sunder

On Fri, Mar 7, 2025 at 7:43 PM Corey Huinker <[email protected]>
wrote:

> I tried to generalize that requirement to all of
>> {schema|data|statistics} for consistency, but that resulted in 9
>> options.
>>
>
> 9 options that resolve to 3 boolean variables. It's not that hard.
>
> And if we add a fourth option set, then we have 12 options. So it's O(3N),
> not O(N^2).
>
> People have scripts now that rely on the existing -only flags, and nearly
> every other potentially optional thing has a -no flag. Let's leverage that.
>


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 21:43         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-08 03:43           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 05:51             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
@ 2025-03-08 07:51               ` Corey Huinker <[email protected]>
  2025-03-08 07:56                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-08 07:51 UTC (permalink / raw)
  To: Hari Krishna Sunder <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Sat, Mar 8, 2025 at 12:52 AM Hari Krishna Sunder <[email protected]>
wrote:

> To improve the performance of pg_dump can we add a new sql function that
> can operate more efficiently than the pg_stats view? It could also take in
> an optional list of oids to filter on.
> This will help speed up the dump and restore within pg18 and future
> upgrades to higher pg versions.
>
>
We can't install functions on the source database - it might be a read
replica.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 21:43         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-08 03:43           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 05:51             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-03-08 07:51               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-08 07:56                 ` Corey Huinker <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Corey Huinker @ 2025-03-08 07:56 UTC (permalink / raw)
  To: Hari Krishna Sunder <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Updated and rebase patches.

0001 is the same as v6-0002, but with proper ACL checks on schemas after
cache lookup

0002 attempts to replace all possible ERRORs in the restore/clear functions
with WARNINGs. This is done with an eye towards reducing the set of things
that could potentially cause an upgrade to fail.

Spoke with Nathan about how best to batch the pg_stats fetches. I'll be
working on that now. Given that, the patch that optimized out
getAttributeStats() calls on indexes without expressions has been
withdrawn. It's a clear incremental gain, and we're looking for a couple
orders of magnitude gain.


Attachments:

  [text/x-patch] v7-0001-Split-relation-into-schemaname-and-relname.patch (65.0K, ../../CADkLM=fLUHPzvVnM4eC8ZdVH0wewXfZmVoVmn4=8pb12+s1v7Q@mail.gmail.com/3-v7-0001-Split-relation-into-schemaname-and-relname.patch)
  download | inline diff:
From 9cd4b4e0e280d0fd8cb120ac105d6e65a491cd7e Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v7 1/2] Split relation into schemaname and relname.

In order to further reduce potential error-failures in restores and
upgrades, replace the numerous casts of fully qualified relation names
into their schema+relname text components.

Further remove the ::name casts on attname and change the expected
datatype to text.

Add an ACL_USAGE check on the namespace oid after it is looked up.
---
 src/include/catalog/pg_proc.dat            |   8 +-
 src/include/statistics/stat_utils.h        |   2 +
 src/backend/statistics/attribute_stats.c   |  87 ++++--
 src/backend/statistics/relation_stats.c    |  65 +++--
 src/backend/statistics/stat_utils.c        |  37 +++
 src/bin/pg_dump/pg_dump.c                  |  25 +-
 src/bin/pg_dump/t/002_pg_dump.pl           |   6 +-
 src/test/regress/expected/stats_import.out | 307 +++++++++++++--------
 src/test/regress/sql/stats_import.sql      | 276 +++++++++++-------
 doc/src/sgml/func.sgml                     |  41 +--
 10 files changed, 566 insertions(+), 288 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cede992b6e2..fdd4b8d7dba 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12443,8 +12443,8 @@
   descr => 'clear statistics on relation',
   proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
   proparallel => 'u', prorettype => 'void',
-  proargtypes => 'regclass',
-  proargnames => '{relation}',
+  proargtypes => 'text text',
+  proargnames => '{schemaname,relname}',
   prosrc => 'pg_clear_relation_stats' },
 { oid => '8461',
   descr => 'restore statistics on attribute',
@@ -12459,8 +12459,8 @@
   descr => 'clear statistics on attribute',
   proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
   proparallel => 'u', prorettype => 'void',
-  proargtypes => 'regclass name bool',
-  proargnames => '{relation,attname,inherited}',
+  proargtypes => 'text text text bool',
+  proargnames => '{schemaname,relname,attname,inherited}',
   prosrc => 'pg_clear_attribute_stats' },
 
 # GiST stratnum implementations
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 0eb4decfcac..cad042c8e4a 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -32,6 +32,8 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 
 extern void stats_lock_check_privileges(Oid reloid);
 
+extern Oid stats_schema_check_privileges(const char *nspname);
+
 extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 											 FunctionCallInfo positional_fcinfo,
 											 struct StatsArgInfo *arginfo);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..f87db2d6102 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -36,7 +36,8 @@
 
 enum attribute_stats_argnum
 {
-	ATTRELATION_ARG = 0,
+	ATTRELSCHEMA_ARG = 0,
+	ATTRELNAME_ARG,
 	ATTNAME_ARG,
 	ATTNUM_ARG,
 	INHERITED_ARG,
@@ -58,8 +59,9 @@ enum attribute_stats_argnum
 
 static struct StatsArgInfo attarginfo[] =
 {
-	[ATTRELATION_ARG] = {"relation", REGCLASSOID},
-	[ATTNAME_ARG] = {"attname", NAMEOID},
+	[ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+	[ATTRELNAME_ARG] = {"relname", TEXTOID},
+	[ATTNAME_ARG] = {"attname", TEXTOID},
 	[ATTNUM_ARG] = {"attnum", INT2OID},
 	[INHERITED_ARG] = {"inherited", BOOLOID},
 	[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +82,8 @@ static struct StatsArgInfo attarginfo[] =
 
 enum clear_attribute_stats_argnum
 {
-	C_ATTRELATION_ARG = 0,
+	C_ATTRELSCHEMA_ARG = 0,
+	C_ATTRELNAME_ARG,
 	C_ATTNAME_ARG,
 	C_INHERITED_ARG,
 	C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +91,9 @@ enum clear_attribute_stats_argnum
 
 static struct StatsArgInfo cleararginfo[] =
 {
-	[C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
-	[C_ATTNAME_ARG] = {"attname", NAMEOID},
+	[C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+	[C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+	[C_ATTNAME_ARG] = {"attname", TEXTOID},
 	[C_INHERITED_ARG] = {"inherited", BOOLOID},
 	[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
 };
@@ -133,6 +137,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
 {
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
 	char	   *attname;
 	AttrNumber	attnum;
@@ -170,8 +177,23 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
-	reloid = PG_GETARG_OID(ATTRELATION_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (nspoid == InvalidOid)
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (reloid == InvalidOid)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -185,21 +207,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
-		Name		attnamename;
-
 		if (!PG_ARGISNULL(ATTNUM_ARG))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
-		attnamename = PG_GETARG_NAME(ATTNAME_ARG);
-		attname = NameStr(*attnamename);
+		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column \"%s\" of relation \"%s\" does not exist",
-							attname, get_rel_name(reloid))));
+					 errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+							attname, nspname, relname)));
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -210,8 +229,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 			!SearchSysCacheExistsAttName(reloid, attname))
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column %d of relation \"%s\" does not exist",
-							attnum, get_rel_name(reloid))));
+					 errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+							attnum, nspname, relname)));
 	}
 	else
 	{
@@ -900,13 +919,33 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
 Datum
 pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 {
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
-	Name		attname;
+	char	   *attname;
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
-	reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (!OidIsValid(nspoid))
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (!OidIsValid(reloid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -916,23 +955,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 
 	stats_lock_check_privileges(reloid);
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	attname = PG_GETARG_NAME(C_ATTNAME_ARG);
-	attnum = get_attnum(reloid, NameStr(*attname));
+	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
-						NameStr(*attname))));
+						attname)));
 
 	if (attnum == InvalidAttrNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
-						NameStr(*attname), get_rel_name(reloid))));
+						attname, get_rel_name(reloid))));
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
 	delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 52dfa477187..fdc69bc93e2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
 
 #include "access/heapam.h"
 #include "catalog/indexing.h"
+#include "catalog/namespace.h"
 #include "statistics/stat_utils.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 
@@ -32,7 +35,8 @@
 
 enum relation_stats_argnum
 {
-	RELATION_ARG = 0,
+	RELSCHEMA_ARG = 0,
+	RELNAME_ARG,
 	RELPAGES_ARG,
 	RELTUPLES_ARG,
 	RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
 
 static struct StatsArgInfo relarginfo[] =
 {
-	[RELATION_ARG] = {"relation", REGCLASSOID},
+	[RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+	[RELNAME_ARG] = {"relname", TEXTOID},
 	[RELPAGES_ARG] = {"relpages", INT4OID},
 	[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
 	[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
 relation_statistics_update(FunctionCallInfo fcinfo)
 {
 	bool		result = true;
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
 	Relation	crel;
 	BlockNumber relpages = 0;
@@ -76,6 +84,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
+	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (!OidIsValid(nspoid))
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (!OidIsValid(reloid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	stats_lock_check_privileges(reloid);
+
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
 		relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +142,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 		update_relallfrozen = true;
 	}
 
-	stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
-	reloid = PG_GETARG_OID(RELATION_ARG);
-
-	if (RecoveryInProgress())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("recovery is in progress"),
-				 errhint("Statistics cannot be modified during recovery.")));
-
-	stats_lock_check_privileges(reloid);
-
 	/*
 	 * Take RowExclusiveLock on pg_class, consistent with
 	 * vac_update_relstats().
@@ -187,20 +210,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 Datum
 pg_clear_relation_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(newfcinfo, 5);
+	LOCAL_FCINFO(newfcinfo, 6);
 
-	InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+	InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
 
-	newfcinfo->args[0].value = PG_GETARG_OID(0);
+	newfcinfo->args[0].value = PG_GETARG_DATUM(0);
 	newfcinfo->args[0].isnull = PG_ARGISNULL(0);
-	newfcinfo->args[1].value = UInt32GetDatum(0);
-	newfcinfo->args[1].isnull = false;
-	newfcinfo->args[2].value = Float4GetDatum(-1.0);
+	newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+	newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+	newfcinfo->args[2].value = UInt32GetDatum(0);
 	newfcinfo->args[2].isnull = false;
-	newfcinfo->args[3].value = UInt32GetDatum(0);
+	newfcinfo->args[3].value = Float4GetDatum(-1.0);
 	newfcinfo->args[3].isnull = false;
 	newfcinfo->args[4].value = UInt32GetDatum(0);
 	newfcinfo->args[4].isnull = false;
+	newfcinfo->args[5].value = UInt32GetDatum(0);
+	newfcinfo->args[5].isnull = false;
 
 	relation_statistics_update(newfcinfo);
 	PG_RETURN_VOID();
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 9647f5108b3..e037d4994e8 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -18,7 +18,9 @@
 
 #include "access/relation.h"
 #include "catalog/index.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "statistics/stat_utils.h"
@@ -213,6 +215,41 @@ stats_lock_check_privileges(Oid reloid)
 	relation_close(table, NoLock);
 }
 
+
+/*
+ * Resolve a schema name into an Oid, ensure that the user has usage privs on
+ * that schema.
+ */
+Oid
+stats_schema_check_privileges(const char *nspname)
+{
+	Oid			nspoid;
+	AclResult	aclresult;
+
+	nspoid = get_namespace_oid(nspname, true);
+
+	if (nspoid == InvalidOid)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_SCHEMA_NAME),
+				 errmsg("schema %s does not exist", nspname)));
+		return InvalidOid;
+	}
+
+	aclresult = object_aclcheck(NamespaceRelationId, nspoid, GetUserId(), ACL_USAGE);
+
+	if (aclresult != ACLCHECK_OK)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for schema %s", nspname)));
+		return InvalidOid;
+	}
+
+	return nspoid;
+}
+
+
 /*
  * Find the argument number for the given argument name, returning -1 if not
  * found.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4f4ad2ee150..6cf2c7d1fe4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10492,7 +10492,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 	PQExpBuffer out;
 	DumpId	   *deps = NULL;
 	int			ndeps = 0;
-	char	   *qualified_name;
 	char		reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
 	int			i_attname;
 	int			i_inherited;
@@ -10558,15 +10557,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 
 	out = createPQExpBuffer();
 
-	qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
 	/* restore relation stats */
 	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
 	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'relation', ");
-	appendStringLiteralAH(out, qualified_name, fout);
-	appendPQExpBufferStr(out, "::regclass,\n");
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n");
+	appendPQExpBufferStr(out, "\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n");
 	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
 	float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
 	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
@@ -10606,9 +10606,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
 		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'relation', ");
-		appendStringLiteralAH(out, qualified_name, fout);
-		appendPQExpBufferStr(out, "::regclass");
+		appendPQExpBufferStr(out, "\t'schemaname', ");
+		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(out, ",\n\t'relname', ");
+		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10620,7 +10621,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 * their attnames are not necessarily stable across dump/reload.
 		 */
 		if (rsinfo->nindAttNames == 0)
-			appendNamedArgument(out, fout, "attname", "name", attname);
+		{
+			appendPQExpBuffer(out, ",\n\t'attname', ");
+			appendStringLiteralAH(out, attname, fout);
+		}
 		else
 		{
 			bool		found = false;
@@ -10700,7 +10704,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							  .deps = deps,
 							  .nDeps = ndeps));
 
-	free(qualified_name);
 	destroyPQExpBuffer(out);
 	destroyPQExpBuffer(query);
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c7bffc1b045..b037f239136 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4725,14 +4725,16 @@ my %tests = (
 		regexp => qr/^
 			\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-			'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+			'schemaname',\s'dump_test',\s+
+			'relname',\s'dup_test_post_data_ix',\s+
 			'relpages',\s'\d+'::integer,\s+
 			'reltuples',\s'\d+'::real,\s+
 			'relallvisible',\s'\d+'::integer\s+
 			\);\s+
 			\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-			'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+			'schemaname',\s'dump_test',\s+
+			'relname',\s'dup_test_post_data_ix',\s+
 			'attnum',\s'2'::smallint,\s+
 			'inherited',\s'f'::boolean,\s+
 			'null_frac',\s'0'::real,\s+
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f46d5e7854..2f1295f2149 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -14,7 +14,8 @@ CREATE TABLE stats_import.test(
 ) WITH (autovacuum_enabled = false);
 SELECT
     pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 18::integer,
         'reltuples', 21::real,
         'relallvisible', 24::integer,
@@ -36,7 +37,7 @@ ORDER BY relname;
  test    |       18 |        21 |            24 |           27
 (1 row)
 
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
  pg_clear_relation_stats 
 -------------------------
  
@@ -45,33 +46,54 @@ SELECT pg_clear_relation_stats('stats_import.test'::regclass);
 --
 -- relstats tests
 --
---- error: relation is wrong type
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid,
+        'relname', 'test',
         'relpages', 17::integer);
-WARNING:  argument "relation" has type "oid", expected type "regclass"
-ERROR:  "relation" cannot be NULL
+ERROR:  "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relpages', 17::integer);
+ERROR:  "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 3.6::float,
+        'relname', 'test',
+        'relpages', 17::integer);
+WARNING:  argument "schemaname" has type "double precision", expected type "text"
+ERROR:  "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relname', 0::oid,
+        'relpages', 17::integer);
+WARNING:  argument "relname" has type "oid", expected type "text"
+ERROR:  "relname" cannot be NULL
 -- error: relation not found
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'nope',
         'relpages', 17::integer);
-ERROR:  could not open relation with OID 0
+WARNING:  Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- error: odd number of variadic arguments cannot be pairs
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible');
 ERROR:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
 -- error: argument name is NULL
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
-        'relation', '0'::oid::regclass,
-        17, '17'::integer);
-ERROR:  name at variadic position 3 has type "integer", expected type "text"
+ERROR:  name at variadic position 5 is NULL
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -84,7 +106,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
 -- regular indexes have special case locking rules
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test_i',
         'relpages', 18::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -132,7 +155,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
 --
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.part_parent_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'part_parent_i',
         'relpages', 2::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -166,7 +190,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
 
 -- ok: set all relstats, with version, no bounds checking
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relpages', '-17'::integer,
         'reltuples', 400::real,
@@ -187,7 +212,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relpages, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '16'::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -204,7 +230,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just reltuples, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'reltuples', '500'::real);
  pg_restore_relation_stats 
 ---------------------------
@@ -221,7 +248,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relallvisible, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible', 5::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -238,7 +266,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: just relallfrozen
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relallfrozen', 3::integer);
  pg_restore_relation_stats 
@@ -256,7 +285,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- warn: bad relpages type, rest updated
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 'nope'::text,
         'reltuples', 400.0::real,
         'relallvisible', 4::integer,
@@ -277,7 +307,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- unrecognized argument name, rest ok
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '171'::integer,
         'nope', 10::integer);
 WARNING:  unrecognized argument name: "nope"
@@ -295,8 +326,7 @@ WHERE oid = 'stats_import.test'::regclass;
 (1 row)
 
 -- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
-    relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
  pg_clear_relation_stats 
 -------------------------
  
@@ -313,87 +343,123 @@ WHERE oid = 'stats_import.test'::regclass;
 -- invalid relkinds for statistics
 CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testseq'::regclass);
+        'schemaname', 'stats_import',
+        'relname', 'testseq');
 ERROR:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
 ERROR:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testview'::regclass);
-ERROR:  cannot modify statistics for relation "testview"
-DETAIL:  This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
 ERROR:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
 --
 -- attribute stats
 --
--- error: object does not exist
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', '0'::oid::regclass,
-    'attname', 'id'::name,
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  could not open relation with OID 0
--- error: relation null
+ERROR:  "schemaname" cannot be NULL
+-- error: schema does not exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', NULL::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'nope',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relation" cannot be NULL
+WARNING:  schema nope does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+ERROR:  "relname" cannot be NULL
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'nope',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+WARNING:  Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', NULL,
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+ERROR:  "relname" cannot be NULL
 -- error: NULL attname
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', NULL::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  must specify either attname or attnum
 -- error: attname doesn't exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'nope'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'nope',
     'inherited', false::boolean,
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "test" does not exist
+ERROR:  column "nope" of relation "stats_import"."test" does not exist
 -- error: both attname and attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  cannot specify both attname and attnum
 -- error: neither attname nor attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  must specify either attname or attnum
 -- error: attribute is system column
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'xmin'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  cannot modify statistics on system column "xmin"
 -- error: inherited null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
 ERROR:  "inherited" cannot be NULL
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'version', 150000::integer,
     'null_frac', 0.2::real,
@@ -421,7 +487,8 @@ AND attname = 'id';
 -- for any stat-having relation.
 --
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.4::real);
@@ -443,8 +510,9 @@ AND attname = 'id';
 
 -- warn: unrecognized argument name, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.2::real,
     'nope', 0.5::real);
@@ -467,8 +535,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 1, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -492,8 +561,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 2, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_vals', '{1,2,3}'::text
@@ -517,8 +587,9 @@ AND attname = 'id';
 
 -- warn: mcf type mismatch, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.22::real,
     'most_common_vals', '{2,1,3}'::text,
@@ -544,8 +615,9 @@ AND attname = 'id';
 
 -- warn: mcv cast failure, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.23::real,
     'most_common_vals', '{2,four,3}'::text,
@@ -570,8 +642,9 @@ AND attname = 'id';
 
 -- ok: mcv+mcf
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'most_common_vals', '{2,1,3}'::text,
     'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -594,8 +667,9 @@ AND attname = 'id';
 
 -- warn: NULL in histogram array, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.24::real,
     'histogram_bounds', '{1,NULL,3,4}'::text
@@ -619,8 +693,9 @@ AND attname = 'id';
 
 -- ok: histogram_bounds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'histogram_bounds', '{1,2,3,4}'::text
     );
@@ -642,8 +717,9 @@ AND attname = 'id';
 
 -- warn: elem_count_histogram null element, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.25::real,
     'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -667,8 +743,9 @@ AND attname = 'tags';
 
 -- ok: elem_count_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.26::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -691,8 +768,9 @@ AND attname = 'tags';
 
 -- warn: range stats on a scalar type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.27::real,
     'range_empty_frac', 0.5::real,
@@ -718,8 +796,9 @@ AND attname = 'id';
 
 -- warn: range_empty_frac range_length_hist null mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.28::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -743,8 +822,9 @@ AND attname = 'arange';
 
 -- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.29::real,
     'range_empty_frac', 0.5::real
@@ -768,8 +848,9 @@ AND attname = 'arange';
 
 -- ok: range_empty_frac + range_length_hist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_empty_frac', 0.5::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -792,8 +873,9 @@ AND attname = 'arange';
 
 -- warn: range bounds histogram on scalar, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.31::real,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -818,8 +900,9 @@ AND attname = 'id';
 
 -- ok: range_bounds_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
     );
@@ -841,8 +924,9 @@ AND attname = 'arange';
 
 -- warn: cannot set most_common_elems for range type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.32::real,
     'most_common_elems', '{3,1}'::text,
@@ -868,8 +952,9 @@ AND attname = 'arange';
 
 -- warn: scalars can't have mcelem, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.33::real,
     'most_common_elems', '{1,3}'::text,
@@ -895,8 +980,9 @@ AND attname = 'id';
 
 -- warn: mcelem / mcelem mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.34::real,
     'most_common_elems', '{one,two}'::text
@@ -920,8 +1006,9 @@ AND attname = 'tags';
 
 -- warn: mcelem / mcelem null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.35::real,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -945,8 +1032,9 @@ AND attname = 'tags';
 
 -- ok: mcelem
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'most_common_elems', '{one,three}'::text,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -969,8 +1057,9 @@ AND attname = 'tags';
 
 -- warn: scalars can't have elem_count_histogram, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.36::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -1022,8 +1111,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
 FROM pg_catalog.pg_stats AS s
 CROSS JOIN LATERAL
     pg_catalog.pg_restore_attribute_stats(
-        'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
-        'attname', s.attname,
+        'schemaname', 'stats_import',
+        'relname', s.tablename::text || '_clone',
+        'attname', s.attname::text,
         'inherited', s.inherited,
         'version', 150000,
         'null_frac', s.null_frac,
@@ -1200,9 +1290,10 @@ AND attname = 'arange';
 (1 row)
 
 SELECT pg_catalog.pg_clear_attribute_stats(
-    relation => 'stats_import.test'::regclass,
-    attname => 'arange'::name,
-    inherited => false::boolean);
+    schemaname => 'stats_import',
+    relname => 'test',
+    attname => 'arange',
+    inherited => false);
  pg_clear_attribute_stats 
 --------------------------
  
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 0ec590688c2..ccdc44e9236 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,7 +17,8 @@ CREATE TABLE stats_import.test(
 
 SELECT
     pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 18::integer,
         'reltuples', 21::real,
         'relallvisible', 24::integer,
@@ -32,37 +33,52 @@ FROM pg_class
 WHERE oid = 'stats_import.test'::regclass
 ORDER BY relname;
 
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
 
 --
 -- relstats tests
 --
 
---- error: relation is wrong type
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid,
+        'relname', 'test',
+        'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 3.6::float,
+        'relname', 'test',
+        'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relname', 0::oid,
         'relpages', 17::integer);
 
 -- error: relation not found
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'nope',
         'relpages', 17::integer);
 
 -- error: odd number of variadic arguments cannot be pairs
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible');
 
 -- error: argument name is NULL
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         NULL, '17'::integer);
 
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
-        'relation', '0'::oid::regclass,
-        17, '17'::integer);
-
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -71,7 +87,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
 -- regular indexes have special case locking rules
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test_i',
         'relpages', 18::integer);
 
 SELECT mode FROM pg_locks
@@ -108,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
 BEGIN;
 
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.part_parent_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'part_parent_i',
         'relpages', 2::integer);
 
 SELECT mode FROM pg_locks
@@ -127,7 +145,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
 
 -- ok: set all relstats, with version, no bounds checking
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relpages', '-17'::integer,
         'reltuples', 400::real,
@@ -140,7 +159,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relpages, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '16'::integer);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -149,7 +169,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just reltuples, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'reltuples', '500'::real);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -158,7 +179,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relallvisible, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible', 5::integer);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,7 +189,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: just relallfrozen
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relallfrozen', 3::integer);
 
@@ -177,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- warn: bad relpages type, rest updated
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 'nope'::text,
         'reltuples', 400.0::real,
         'relallvisible', 4::integer,
@@ -189,7 +213,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- unrecognized argument name, rest ok
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '171'::integer,
         'nope', 10::integer);
 
@@ -198,8 +223,7 @@ FROM pg_class
 WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
-    relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
 
 SELECT relpages, reltuples, relallvisible
 FROM pg_class
@@ -209,48 +233,70 @@ WHERE oid = 'stats_import.test'::regclass;
 CREATE SEQUENCE stats_import.testseq;
 
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testseq'::regclass);
+        'schemaname', 'stats_import',
+        'relname', 'testseq');
 
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
 
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 
-SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
 
 --
 -- attribute stats
 --
 
--- error: object does not exist
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', '0'::oid::regclass,
-    'attname', 'id'::name,
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relation null
+-- error: schema does not exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', NULL::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'nope',
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'nope',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', NULL,
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: NULL attname
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', NULL::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: attname doesn't exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'nope'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'nope',
     'inherited', false::boolean,
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
@@ -258,36 +304,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
 
 -- error: both attname and attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: neither attname nor attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: attribute is system column
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'xmin'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: inherited null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
 
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'version', 150000::integer,
     'null_frac', 0.2::real,
@@ -307,7 +358,8 @@ AND attname = 'id';
 -- for any stat-having relation.
 --
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.4::real);
@@ -321,8 +373,9 @@ AND attname = 'id';
 
 -- warn: unrecognized argument name, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.2::real,
     'nope', 0.5::real);
@@ -336,8 +389,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 1, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -352,8 +406,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 2, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_vals', '{1,2,3}'::text
@@ -368,8 +423,9 @@ AND attname = 'id';
 
 -- warn: mcf type mismatch, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.22::real,
     'most_common_vals', '{2,1,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
 
 -- warn: mcv cast failure, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.23::real,
     'most_common_vals', '{2,four,3}'::text,
@@ -402,8 +459,9 @@ AND attname = 'id';
 
 -- ok: mcv+mcf
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'most_common_vals', '{2,1,3}'::text,
     'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -418,8 +476,9 @@ AND attname = 'id';
 
 -- warn: NULL in histogram array, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.24::real,
     'histogram_bounds', '{1,NULL,3,4}'::text
@@ -434,8 +493,9 @@ AND attname = 'id';
 
 -- ok: histogram_bounds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'histogram_bounds', '{1,2,3,4}'::text
     );
@@ -449,8 +509,9 @@ AND attname = 'id';
 
 -- warn: elem_count_histogram null element, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.25::real,
     'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -465,8 +526,9 @@ AND attname = 'tags';
 
 -- ok: elem_count_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.26::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -481,8 +543,9 @@ AND attname = 'tags';
 
 -- warn: range stats on a scalar type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.27::real,
     'range_empty_frac', 0.5::real,
@@ -498,8 +561,9 @@ AND attname = 'id';
 
 -- warn: range_empty_frac range_length_hist null mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.28::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -514,8 +578,9 @@ AND attname = 'arange';
 
 -- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.29::real,
     'range_empty_frac', 0.5::real
@@ -530,8 +595,9 @@ AND attname = 'arange';
 
 -- ok: range_empty_frac + range_length_hist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_empty_frac', 0.5::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -546,8 +612,9 @@ AND attname = 'arange';
 
 -- warn: range bounds histogram on scalar, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.31::real,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -562,8 +629,9 @@ AND attname = 'id';
 
 -- ok: range_bounds_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
     );
@@ -577,8 +645,9 @@ AND attname = 'arange';
 
 -- warn: cannot set most_common_elems for range type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.32::real,
     'most_common_elems', '{3,1}'::text,
@@ -594,8 +663,9 @@ AND attname = 'arange';
 
 -- warn: scalars can't have mcelem, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.33::real,
     'most_common_elems', '{1,3}'::text,
@@ -611,8 +681,9 @@ AND attname = 'id';
 
 -- warn: mcelem / mcelem mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.34::real,
     'most_common_elems', '{one,two}'::text
@@ -627,8 +698,9 @@ AND attname = 'tags';
 
 -- warn: mcelem / mcelem null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.35::real,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -643,8 +715,9 @@ AND attname = 'tags';
 
 -- ok: mcelem
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'most_common_elems', '{one,three}'::text,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -659,8 +732,9 @@ AND attname = 'tags';
 
 -- warn: scalars can't have elem_count_histogram, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.36::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -707,8 +781,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
 FROM pg_catalog.pg_stats AS s
 CROSS JOIN LATERAL
     pg_catalog.pg_restore_attribute_stats(
-        'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
-        'attname', s.attname,
+        'schemaname', 'stats_import',
+        'relname', s.tablename::text || '_clone',
+        'attname', s.attname::text,
         'inherited', s.inherited,
         'version', 150000,
         'null_frac', s.null_frac,
@@ -853,9 +928,10 @@ AND inherited = false
 AND attname = 'arange';
 
 SELECT pg_catalog.pg_clear_attribute_stats(
-    relation => 'stats_import.test'::regclass,
-    attname => 'arange'::name,
-    inherited => false::boolean);
+    schemaname => 'stats_import',
+    relname => 'test',
+    attname => 'arange',
+    inherited => false);
 
 SELECT COUNT(*)
 FROM pg_stats
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 51dd8ad6571..63a260a8ff8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30348,22 +30348,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <structname>mytable</structname>:
 <programlisting>
  SELECT pg_restore_relation_stats(
-    'relation',  'mytable'::regclass,
-    'relpages',  173::integer,
-    'reltuples', 10000::real);
+    'schemaname', 'myschema',
+    'relname',    'mytable',
+    'relpages',   173::integer,
+    'reltuples',  10000::real);
 </programlisting>
         </para>
         <para>
-         The argument <literal>relation</literal> with a value of type
-         <type>regclass</type> is required, and specifies the table. Other
-         arguments are the names and values of statistics corresponding to
-         certain columns in <link
+         The arguments <literal>schemaname</literal> with a value of type
+         <type>regclass</type> and <literal>relname</literal> are required,
+         and specifies the table. Other arguments are the names and values
+         of statistics corresponding to certain columns in <link
          linkend="catalog-pg-class"><structname>pg_class</structname></link>.
          The currently-supported relation statistics are
          <literal>relpages</literal> with a value of type
          <type>integer</type>, <literal>reltuples</literal> with a value of
-         type <type>real</type>, and <literal>relallvisible</literal> with a
-         value of type <type>integer</type>.
+         type <type>real</type>, <literal>relallvisible</literal> with a
+         value of type <type>integer</type>, and <literal>relallfrozen</literal>
+         with a value of type <type>integer</type>.
         </para>
         <para>
          Additionally, this function accepts argument name
@@ -30391,7 +30393,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <indexterm>
           <primary>pg_clear_relation_stats</primary>
          </indexterm>
-         <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+         <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
          <returnvalue>void</returnvalue>
         </para>
         <para>
@@ -30440,16 +30442,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <structname>mytable</structname>:
 <programlisting>
  SELECT pg_restore_attribute_stats(
-    'relation',    'mytable'::regclass,
-    'attname',     'col1'::name,
-    'inherited',   false,
-    'avg_width',   125::integer,
-    'null_frac',   0.5::real);
+    'schemaname', 'myschema',
+    'relname',    'mytable',
+    'attname',    'col1',
+    'inherited',  false,
+    'avg_width',  125::integer,
+    'null_frac',  0.5::real);
 </programlisting>
         </para>
         <para>
-         The required arguments are <literal>relation</literal> with a value
-         of type <type>regclass</type>, which specifies the table; either
+         The required arguments are <literal>schemaname</literal> with a value
+         of type <type>regclass</type> and <literal>relname</literal> with a value
+         of type <type>text</type> which specify the table; either
          <literal>attname</literal> with a value of type <type>name</type> or
          <literal>attnum</literal> with a value of type <type>smallint</type>,
          which specifies the column; and <literal>inherited</literal>, which
@@ -30485,7 +30489,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
           <primary>pg_clear_attribute_stats</primary>
          </indexterm>
          <function>pg_clear_attribute_stats</function> (
-         <parameter>relation</parameter> <type>regclass</type>,
+         <parameter>schemaname</parameter> <type>text</type>,
+         <parameter>relname</parameter> <type>text</type>,
          <parameter>attname</parameter> <type>name</type>,
          <parameter>inherited</parameter> <type>boolean</type> )
          <returnvalue>void</returnvalue>

base-commit: 21f653cc0024100f8ecc279162631f2b1ba8c46c
-- 
2.48.1



  [text/x-patch] v7-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch (29.8K, ../../CADkLM=fLUHPzvVnM4eC8ZdVH0wewXfZmVoVmn4=8pb12+s1v7Q@mail.gmail.com/4-v7-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch)
  download | inline diff:
From 4d8d76b78b87f53d0adbd6781a2a66beac5bc264 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v7 2/2] Downgrade as man pg_restore_*_stats errors to
 warnings.

We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
 src/include/statistics/stat_utils.h        |   4 +-
 src/backend/statistics/attribute_stats.c   | 124 +++++++++++-----
 src/backend/statistics/relation_stats.c    |  10 +-
 src/backend/statistics/stat_utils.c        |  51 +++++--
 src/test/regress/expected/stats_import.out | 163 ++++++++++++++++-----
 src/test/regress/sql/stats_import.sql      |  36 ++---
 6 files changed, 277 insertions(+), 111 deletions(-)

diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index cad042c8e4a..298cbae3436 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
 	Oid			argtype;
 };
 
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
 									 struct StatsArgInfo *arginfo,
 									 int argnum);
 extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 								 struct StatsArgInfo *arginfo,
 								 int argnum1, int argnum2);
 
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
 
 extern Oid stats_schema_check_privileges(const char *nspname);
 
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f87db2d6102..4f9bc18f8c6 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
 
 static bool attribute_statistics_update(FunctionCallInfo fcinfo);
 static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
 							   Oid *atttypid, int32 *atttypmod,
 							   char *atttyptype, Oid *atttypcoll,
 							   Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
  * stored as an anyarray, and the representation of the array needs to store
  * the correct element type, which must be derived from the attribute.
  *
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
  */
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -149,8 +151,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	HeapTuple	statup;
 
 	Oid			atttypid = InvalidOid;
-	int32		atttypmod;
-	char		atttyptype;
+	int32		atttypmod = -1;
+	char		atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
 	Oid			atttypcoll = InvalidOid;
 	Oid			eq_opr = InvalidOid;
 	Oid			lt_opr = InvalidOid;
@@ -177,17 +179,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+		return false;
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
-	if (nspoid == InvalidOid)
+	if (!OidIsValid(nspoid))
 		return false;
 
 	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
 	reloid = get_relname_relid(relname, nspoid);
-	if (reloid == InvalidOid)
+	if (!OidIsValid(reloid))
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -196,29 +200,39 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		return false;
+	}
 
 	/* lock before looking up attribute */
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
 		if (!PG_ARGISNULL(ATTNUM_ARG))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
+			return false;
+		}
 		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
 							attname, nspname, relname)));
+			return false;
+		}
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -227,27 +241,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		/* annoyingly, get_attname doesn't check attisdropped */
 		if (attname == NULL ||
 			!SearchSysCacheExistsAttName(reloid, attname))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column %d of relation \"%s\".\"%s\" does not exist",
 							attnum, nspname, relname)));
+			return false;
+		}
 	}
 	else
 	{
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("must specify either attname or attnum")));
-		attname = NULL;			/* keep compiler quiet */
-		attnum = 0;
+		return false;
 	}
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
+		return false;
+	}
 
-	stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+		return false;
 	inherited = PG_GETARG_BOOL(INHERITED_ARG);
 
 	/*
@@ -296,10 +316,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* derive information from attribute */
-	get_attr_stat_type(reloid, attnum,
-					   &atttypid, &atttypmod,
-					   &atttyptype, &atttypcoll,
-					   &eq_opr, &lt_opr);
+	if (!get_attr_stat_type(reloid, attnum,
+							&atttypid, &atttypmod,
+							&atttyptype, &atttypcoll,
+							&eq_opr, &lt_opr))
+		result = false;
 
 	/* if needed, derive element type */
 	if (do_mcelem || do_dechist)
@@ -579,7 +600,7 @@ get_attr_expr(Relation rel, int attnum)
 /*
  * Derive type information from the attribute.
  */
-static void
+static bool
 get_attr_stat_type(Oid reloid, AttrNumber attnum,
 				   Oid *atttypid, int32 *atttypmod,
 				   char *atttyptype, Oid *atttypcoll,
@@ -596,18 +617,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 
 	/* Attribute not found */
 	if (!HeapTupleIsValid(atup))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	attr = (Form_pg_attribute) GETSTRUCT(atup);
 
 	if (attr->attisdropped)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	expr = get_attr_expr(rel, attr->attnum);
 
@@ -656,6 +685,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 		*atttypcoll = DEFAULT_COLLATION_OID;
 
 	relation_close(rel, NoLock);
+	return true;
 }
 
 /*
@@ -781,6 +811,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
 	if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
 		slotidx = first_empty;
 
+	/*
+	 * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+	 * statistic kinds, so this can safely remain an ERROR for now.
+	 */
 	if (slotidx >= STATISTIC_NUM_SLOTS)
 		ereport(ERROR,
 				(errmsg("maximum number of statistics slots exceeded: %d",
@@ -927,15 +961,19 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+		PG_RETURN_VOID();
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
 	if (!OidIsValid(nspoid))
-		return false;
+		PG_RETURN_VOID();
 
 	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
 	reloid = get_relname_relid(relname, nspoid);
@@ -944,31 +982,41 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
-		return false;
+		PG_RETURN_VOID();
 	}
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		PG_RETURN_VOID();
+	}
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		PG_RETURN_VOID();
 
 	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
 	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
 						attname)));
+		PG_RETURN_VOID();
+	}
 
 	if (attnum == InvalidAttrNumber)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
 						attname, get_rel_name(reloid))));
+		PG_RETURN_VOID();
+	}
 
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index fdc69bc93e2..49109cf721d 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -84,8 +84,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
-	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+		return false;
+
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
@@ -108,7 +111,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e037d4994e8..dd9d88ac1c5 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -34,16 +34,20 @@
 /*
  * Ensure that a given argument is not null.
  */
-void
+bool
 stats_check_required_arg(FunctionCallInfo fcinfo,
 						 struct StatsArgInfo *arginfo,
 						 int argnum)
 {
 	if (PG_ARGISNULL(argnum))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("\"%s\" cannot be NULL",
 						arginfo[argnum].argname)));
+		return false;
+	}
+	return true;
 }
 
 /*
@@ -128,13 +132,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
  *   - the role owns the current database and the relation is not shared
  *   - the role has the MAINTAIN privilege on the relation
  */
-void
+bool
 stats_lock_check_privileges(Oid reloid)
 {
 	Relation	table;
 	Oid			table_oid = reloid;
 	Oid			index_oid = InvalidOid;
 	LOCKMODE	index_lockmode = NoLock;
+	bool		ok = true;
 
 	/*
 	 * For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -174,14 +179,15 @@ stats_lock_check_privileges(Oid reloid)
 		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
-			ereport(ERROR,
+			ereport(WARNING,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot modify statistics for relation \"%s\"",
 							RelationGetRelationName(table)),
 					 errdetail_relkind_not_supported(table->rd_rel->relkind)));
+		ok = false;
 	}
 
-	if (OidIsValid(index_oid))
+	if (ok && (OidIsValid(index_oid)))
 	{
 		Relation	index;
 
@@ -194,25 +200,33 @@ stats_lock_check_privileges(Oid reloid)
 		relation_close(index, NoLock);
 	}
 
-	if (table->rd_rel->relisshared)
-		ereport(ERROR,
+	if (ok && (table->rd_rel->relisshared))
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics for shared relation")));
+		ok = false;
+	}
 
-	if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+	if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
 	{
 		AclResult	aclresult = pg_class_aclcheck(RelationGetRelid(table),
 												  GetUserId(),
 												  ACL_MAINTAIN);
 
 		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult,
-						   get_relkind_objtype(table->rd_rel->relkind),
-						   NameStr(table->rd_rel->relname));
+		{
+			ereport(WARNING,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						errmsg("permission denied for relation %s",
+							   NameStr(table->rd_rel->relname))));
+			ok = false;
+		}
 	}
 
 	/* retain lock on table */
 	relation_close(table, NoLock);
+	return ok;
 }
 
 
@@ -318,9 +332,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 								  &args, &types, &argnulls);
 
 	if (nargs % 2 != 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				errmsg("variadic arguments must be name/value pairs"),
 				errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+		return false;
+	}
 
 	/*
 	 * For each argument name/value pair, find corresponding positional
@@ -333,14 +350,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 		char	   *argname;
 
 		if (argnulls[i])
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d is NULL", i + 1)));
+			return false;
+		}
 
 		if (types[i] != TEXTOID)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
 							i + 1, format_type_be(types[i]),
 							format_type_be(TEXTOID))));
+			return false;
+		}
 
 		if (argnulls[i + 1])
 			continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 2f1295f2149..6551d6bf099 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,31 +46,51 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 --
 -- relstats tests
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
-ERROR:  "schemaname" cannot be NULL
--- error: relname missing
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
-ERROR:  "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 WARNING:  argument "schemaname" has type "double precision", expected type "text"
-ERROR:  "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 WARNING:  argument "relname" has type "oid", expected type "text"
-ERROR:  "relname" cannot be NULL
--- error: relation not found
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
@@ -81,19 +101,30 @@ WARNING:  Relation "stats_import"."nope" not found.
  f
 (1 row)
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
-ERROR:  variadic arguments must be name/value pairs
+WARNING:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 5 is NULL
+WARNING:  name at variadic position 5 is NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -345,26 +376,46 @@ CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR:  cannot modify statistics for relation "testview"
+WARNING:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 --
 -- attribute stats
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -377,14 +428,19 @@ WARNING:  schema nope does not exist
  f
 (1 row)
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: relname does not exist
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -397,23 +453,33 @@ WARNING:  Relation "stats_import"."nope" not found.
  f
 (1 row)
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: NULL attname
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attname doesn't exist
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -422,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "stats_import"."test" does not exist
--- error: both attname and attnum
+WARNING:  column "nope" of relation "stats_import"."test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -431,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING:  cannot specify both attname and attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attribute is system column
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING:  cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
-ERROR:  "inherited" cannot be NULL
+WARNING:  "inherited" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index ccdc44e9236..dbbebce1673 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 -- relstats tests
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
 
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 
--- error: relation not found
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
 
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
 -- attribute stats
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: NULL attname
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
 
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: inherited null
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
-- 
2.48.1



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
@ 2025-03-08 03:40         ` Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-08 03:40 UTC (permalink / raw)
  To: Robert Treat <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
>
> if you want everything --include=schema,data,statistics (presumably
> redundant with the default behavior)
> if you want schema only --include=schema
> if you want "everything except schema" --include=data,statistics
>

Until we add a fourth option, and then it becomes completely ambiguous as
to whether you wanted data+statstics, or you not-wanted schema.



And if someday, for example, there is ever agreement on including role
> information with normal pg_dump, you add "roles" as an option to be
> parsed via --include without having to create any new flags.
>

This is pushing a burden onto our customers for a parsing convenience.

-1.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-08 15:56           ` Robert Treat <[email protected]>
  2025-03-08 19:09             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Robert Treat @ 2025-03-08 15:56 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, Mar 7, 2025 at 10:40 PM Corey Huinker <[email protected]>
wrote:

>
>> if you want everything --include=schema,data,statistics (presumably
>> redundant with the default behavior)
>> if you want schema only --include=schema
>> if you want "everything except schema" --include=data,statistics
>>
>
> Until we add a fourth option, and then it becomes completely ambiguous as
> to whether you wanted data+statstics, or you not-wanted schema.
>
>
except it is perfectly clear that you *asked for* data and statistics, so
you get what you asked for. however the user conjures in their heads what
they are looking for, the logic is simple, you get what you asked for.


>
>
> And if someday, for example, there is ever agreement on including role
>> information with normal pg_dump, you add "roles" as an option to be
>> parsed via --include without having to create any new flags.
>>
>
> This is pushing a burden onto our customers for a parsing convenience.
>
>
In the UX world, the general pattern is people start to get overwhelmed
once you get over a 1/2 dozen options (I think that's based on Miller's
law, but might be mis-remembering); we are already at 9 for this use case.
So really it is quite the opposite, we'd be reducing the burden on
customers by simplifying the interface rather than just throwing out every
possible combination and saying "you figure it out".


Robert Treat
https://xzilla.net


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
@ 2025-03-08 19:09             ` Corey Huinker <[email protected]>
  2025-03-25 05:32               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-08 19:09 UTC (permalink / raw)
  To: Robert Treat <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> Until we add a fourth option, and then it becomes completely ambiguous as
>> to whether you wanted data+statstics, or you not-wanted schema.
>>
>>
> except it is perfectly clear that you *asked for* data and statistics, so
> you get what you asked for. however the user conjures in their heads what
> they are looking for, the logic is simple, you get what you asked for.
>

They *asked for* that because they didn't have the mechanism to say "hold
the mayo" or "everything except pickles". That's reducing their choice, and
then blaming them for their choice.

In the UX world, the general pattern is people start to get overwhelmed
> once you get over a 1/2 dozen options (I think that's based on Miller's
> law, but might be mis-remembering); we are already at 9 for this use case.
> So really it is quite the opposite, we'd be reducing the burden on
> customers by simplifying the interface rather than just throwing out every
> possible combination and saying "you figure it out".
>

Except that those options are easily grouped into families. I see that
there's a --no-comments flag, so why wouldn't there be a --no-statistics
flag? Lots of $thing have a --no-$thing. That's the established UX pattern
_working_. The user learned that pattern and we shouldn't punish them by
changing it for our own parsing convenience.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 19:09             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-25 05:32               ` Jeff Davis <[email protected]>
  2025-03-25 17:51                 ` Re: Statistics Import and Export Robert Treat <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-25 05:32 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Sat, 2025-03-08 at 14:09 -0500, Corey Huinker wrote:
> > 
> > except it is perfectly clear that you *asked for* data and
> > statistics, so you get what you asked for. however the user
> > conjures in their heads what they are looking for, the logic is
> > simple, you get what you asked for. 
> > 
> 
> 
> They *asked for* that because they didn't have the mechanism to say
> "hold the mayo" or "everything except pickles". That's reducing their
> choice, and then blaming them for their choice.

Can we reach a decision here and move forward?

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 19:09             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 05:32               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-25 17:51                 ` Robert Treat <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Robert Treat @ 2025-03-25 17:51 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Tue, Mar 25, 2025 at 1:32 AM Jeff Davis <[email protected]> wrote:

> On Sat, 2025-03-08 at 14:09 -0500, Corey Huinker wrote:
> > >
> > > except it is perfectly clear that you *asked for* data and
> > > statistics, so you get what you asked for. however the user
> > > conjures in their heads what they are looking for, the logic is
> > > simple, you get what you asked for.
> > >
> >
> >
> > They *asked for* that because they didn't have the mechanism to say
> > "hold the mayo" or "everything except pickles". That's reducing their
> > choice, and then blaming them for their choice.
>
> Can we reach a decision here and move forward?
>
>
AFAIK the issue has been settled, or at the least we've agreed to move on.


Robert Treat
https://xzilla.net


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
@ 2025-03-09 17:00             ` Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-09 17:00 UTC (permalink / raw)
  To: Robert Treat <[email protected]>; Corey Huinker <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Sat, 2025-03-08 at 10:56 -0500, Robert Treat wrote:
> In the UX world, the general pattern is people start to get
> overwhelmed once you get over a 1/2 dozen options (I think that's
> based on Miller's law, but might be mis-remembering); we are already
> at 9 for this use case. So really it is quite the opposite, we'd be
> reducing the burden on customers by simplifying the interface rather
> than just throwing out every possible combination and saying "you
> figure it out". 

To be clear about your proposal:

* --include conflicts with --schema-only and --data-only
* --include overrides any default

is that right?

Thoughts on how we should document when/how to use --section vs --
include? Granted, that might be a point of confusion regardless of the
options we offer.

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-14 20:03               ` Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 20:33                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Corey Huinker @ 2025-03-14 20:03 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

New patches and a rebase.

0001 - no changes, but the longer I go the more I'm certain this is
something we want to do.
0002- same as 0001

0003 -

Storing the restore function calls in the archive entry hogged a lot of
memory and made people nervous. This introduces a new function pointer that
generates those restore SQL calls right before they're written to disk,
thus reducing the memory load from "stats for every object to be dumped" to
just one object. Thanks to Nathan for diagnosing some weird quirks with
various formats.

0004 -

This replaces the query in the prepared statement with one that batches
them 100 relations at a time, and then maintains that result set until it
is consumed. It seems to have obvious speedups.


database pg14, 100k tables x 2 columns each:

0004: 34.5s with statistics, 25.04s without
0003: 42.23s with statistics, 24.29s without
0002: 42.25s with statistics, 23.17s without


Gory details:

PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump --file=tip.run1.dump
5.45user 2.38system 0:34.50elapsed 22%CPU (0avgtext+0avgdata
912680maxresident)k
0inputs+2105736outputs (0major+245090minor)pagefaults 0swaps

PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump --no-statistics
--file=tip.nostats.run1.dump
4.36user 2.05system 0:25.04elapsed 25%CPU (0avgtext+0avgdata
702488maxresident)k
0inputs+1643048outputs (0major+192512minor)pagefaults 0swaps

PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump
--file=nobatch.run1.dump
5.60user 3.95system 0:42.23elapsed 22%CPU (0avgtext+0avgdata
902424maxresident)k
0inputs+2105672outputs (0major+242536minor)pagefaults 0swaps

PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump --no-statistics
--file=nobatch-nostats.run1.dump
4.38user 2.13system 0:24.29elapsed 26%CPU (0avgtext+0avgdata
702292maxresident)k
48inputs+1642952outputs (0major+192515minor)pagefaults 0swaps

PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump
--file=nostmtfn.run1.dump
6.01user 4.47system 0:42.25elapsed 24%CPU (0avgtext+0avgdata
1089784maxresident)k
0inputs+2106840outputs (0major+289407minor)pagefaults 0swaps

PGSERVICE=benchmark14 time /usr/local/pgsql/bin/pg_dump --no-statistics
--file=nostmtfn-nostats.run1.dump
4.35user 2.13system 0:23.17elapsed 27%CPU (0avgtext+0avgdata
690000maxresident)k
0inputs+1642952outputs (0major+189383minor)pagefaults 0swaps


Attachments:

  [text/x-patch] v8-0001-Split-relation-into-schemaname-and-relname.patch (65.0K, ../../CADkLM=c+r05srPy9w+-+nbmLEo15dKXYQ03Q_xyK+riJerigLQ@mail.gmail.com/3-v8-0001-Split-relation-into-schemaname-and-relname.patch)
  download | inline diff:
From a2c68b8390cf137323f449a4bc826ad66bada0bb Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v8 1/4] Split relation into schemaname and relname.

In order to further reduce potential error-failures in restores and
upgrades, replace the numerous casts of fully qualified relation names
into their schema+relname text components.

Further remove the ::name casts on attname and change the expected
datatype to text.

Add an ACL_USAGE check on the namespace oid after it is looked up.
---
 src/include/catalog/pg_proc.dat            |   8 +-
 src/include/statistics/stat_utils.h        |   2 +
 src/backend/statistics/attribute_stats.c   |  87 ++++--
 src/backend/statistics/relation_stats.c    |  65 +++--
 src/backend/statistics/stat_utils.c        |  37 +++
 src/bin/pg_dump/pg_dump.c                  |  25 +-
 src/bin/pg_dump/t/002_pg_dump.pl           |   6 +-
 src/test/regress/expected/stats_import.out | 307 +++++++++++++--------
 src/test/regress/sql/stats_import.sql      | 276 +++++++++++-------
 doc/src/sgml/func.sgml                     |  41 +--
 10 files changed, 566 insertions(+), 288 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 890822eaf79..8dee321d248 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12453,8 +12453,8 @@
   descr => 'clear statistics on relation',
   proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
   proparallel => 'u', prorettype => 'void',
-  proargtypes => 'regclass',
-  proargnames => '{relation}',
+  proargtypes => 'text text',
+  proargnames => '{schemaname,relname}',
   prosrc => 'pg_clear_relation_stats' },
 { oid => '8461',
   descr => 'restore statistics on attribute',
@@ -12469,8 +12469,8 @@
   descr => 'clear statistics on attribute',
   proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
   proparallel => 'u', prorettype => 'void',
-  proargtypes => 'regclass name bool',
-  proargnames => '{relation,attname,inherited}',
+  proargtypes => 'text text text bool',
+  proargnames => '{schemaname,relname,attname,inherited}',
   prosrc => 'pg_clear_attribute_stats' },
 
 # GiST stratnum implementations
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 0eb4decfcac..cad042c8e4a 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -32,6 +32,8 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 
 extern void stats_lock_check_privileges(Oid reloid);
 
+extern Oid stats_schema_check_privileges(const char *nspname);
+
 extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 											 FunctionCallInfo positional_fcinfo,
 											 struct StatsArgInfo *arginfo);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..f87db2d6102 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -36,7 +36,8 @@
 
 enum attribute_stats_argnum
 {
-	ATTRELATION_ARG = 0,
+	ATTRELSCHEMA_ARG = 0,
+	ATTRELNAME_ARG,
 	ATTNAME_ARG,
 	ATTNUM_ARG,
 	INHERITED_ARG,
@@ -58,8 +59,9 @@ enum attribute_stats_argnum
 
 static struct StatsArgInfo attarginfo[] =
 {
-	[ATTRELATION_ARG] = {"relation", REGCLASSOID},
-	[ATTNAME_ARG] = {"attname", NAMEOID},
+	[ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+	[ATTRELNAME_ARG] = {"relname", TEXTOID},
+	[ATTNAME_ARG] = {"attname", TEXTOID},
 	[ATTNUM_ARG] = {"attnum", INT2OID},
 	[INHERITED_ARG] = {"inherited", BOOLOID},
 	[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +82,8 @@ static struct StatsArgInfo attarginfo[] =
 
 enum clear_attribute_stats_argnum
 {
-	C_ATTRELATION_ARG = 0,
+	C_ATTRELSCHEMA_ARG = 0,
+	C_ATTRELNAME_ARG,
 	C_ATTNAME_ARG,
 	C_INHERITED_ARG,
 	C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +91,9 @@ enum clear_attribute_stats_argnum
 
 static struct StatsArgInfo cleararginfo[] =
 {
-	[C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
-	[C_ATTNAME_ARG] = {"attname", NAMEOID},
+	[C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+	[C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+	[C_ATTNAME_ARG] = {"attname", TEXTOID},
 	[C_INHERITED_ARG] = {"inherited", BOOLOID},
 	[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
 };
@@ -133,6 +137,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
 {
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
 	char	   *attname;
 	AttrNumber	attnum;
@@ -170,8 +177,23 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
-	reloid = PG_GETARG_OID(ATTRELATION_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (nspoid == InvalidOid)
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (reloid == InvalidOid)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -185,21 +207,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
-		Name		attnamename;
-
 		if (!PG_ARGISNULL(ATTNUM_ARG))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
-		attnamename = PG_GETARG_NAME(ATTNAME_ARG);
-		attname = NameStr(*attnamename);
+		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column \"%s\" of relation \"%s\" does not exist",
-							attname, get_rel_name(reloid))));
+					 errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+							attname, nspname, relname)));
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -210,8 +229,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 			!SearchSysCacheExistsAttName(reloid, attname))
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column %d of relation \"%s\" does not exist",
-							attnum, get_rel_name(reloid))));
+					 errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+							attnum, nspname, relname)));
 	}
 	else
 	{
@@ -900,13 +919,33 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
 Datum
 pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 {
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
-	Name		attname;
+	char	   *attname;
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
-	reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (!OidIsValid(nspoid))
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (!OidIsValid(reloid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -916,23 +955,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 
 	stats_lock_check_privileges(reloid);
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	attname = PG_GETARG_NAME(C_ATTNAME_ARG);
-	attnum = get_attnum(reloid, NameStr(*attname));
+	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
-						NameStr(*attname))));
+						attname)));
 
 	if (attnum == InvalidAttrNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
-						NameStr(*attname), get_rel_name(reloid))));
+						attname, get_rel_name(reloid))));
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
 	delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 52dfa477187..fdc69bc93e2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
 
 #include "access/heapam.h"
 #include "catalog/indexing.h"
+#include "catalog/namespace.h"
 #include "statistics/stat_utils.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 
@@ -32,7 +35,8 @@
 
 enum relation_stats_argnum
 {
-	RELATION_ARG = 0,
+	RELSCHEMA_ARG = 0,
+	RELNAME_ARG,
 	RELPAGES_ARG,
 	RELTUPLES_ARG,
 	RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
 
 static struct StatsArgInfo relarginfo[] =
 {
-	[RELATION_ARG] = {"relation", REGCLASSOID},
+	[RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+	[RELNAME_ARG] = {"relname", TEXTOID},
 	[RELPAGES_ARG] = {"relpages", INT4OID},
 	[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
 	[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
 relation_statistics_update(FunctionCallInfo fcinfo)
 {
 	bool		result = true;
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
 	Relation	crel;
 	BlockNumber relpages = 0;
@@ -76,6 +84,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
+	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (!OidIsValid(nspoid))
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (!OidIsValid(reloid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	stats_lock_check_privileges(reloid);
+
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
 		relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +142,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 		update_relallfrozen = true;
 	}
 
-	stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
-	reloid = PG_GETARG_OID(RELATION_ARG);
-
-	if (RecoveryInProgress())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("recovery is in progress"),
-				 errhint("Statistics cannot be modified during recovery.")));
-
-	stats_lock_check_privileges(reloid);
-
 	/*
 	 * Take RowExclusiveLock on pg_class, consistent with
 	 * vac_update_relstats().
@@ -187,20 +210,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 Datum
 pg_clear_relation_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(newfcinfo, 5);
+	LOCAL_FCINFO(newfcinfo, 6);
 
-	InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+	InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
 
-	newfcinfo->args[0].value = PG_GETARG_OID(0);
+	newfcinfo->args[0].value = PG_GETARG_DATUM(0);
 	newfcinfo->args[0].isnull = PG_ARGISNULL(0);
-	newfcinfo->args[1].value = UInt32GetDatum(0);
-	newfcinfo->args[1].isnull = false;
-	newfcinfo->args[2].value = Float4GetDatum(-1.0);
+	newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+	newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+	newfcinfo->args[2].value = UInt32GetDatum(0);
 	newfcinfo->args[2].isnull = false;
-	newfcinfo->args[3].value = UInt32GetDatum(0);
+	newfcinfo->args[3].value = Float4GetDatum(-1.0);
 	newfcinfo->args[3].isnull = false;
 	newfcinfo->args[4].value = UInt32GetDatum(0);
 	newfcinfo->args[4].isnull = false;
+	newfcinfo->args[5].value = UInt32GetDatum(0);
+	newfcinfo->args[5].isnull = false;
 
 	relation_statistics_update(newfcinfo);
 	PG_RETURN_VOID();
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 9647f5108b3..e037d4994e8 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -18,7 +18,9 @@
 
 #include "access/relation.h"
 #include "catalog/index.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "statistics/stat_utils.h"
@@ -213,6 +215,41 @@ stats_lock_check_privileges(Oid reloid)
 	relation_close(table, NoLock);
 }
 
+
+/*
+ * Resolve a schema name into an Oid, ensure that the user has usage privs on
+ * that schema.
+ */
+Oid
+stats_schema_check_privileges(const char *nspname)
+{
+	Oid			nspoid;
+	AclResult	aclresult;
+
+	nspoid = get_namespace_oid(nspname, true);
+
+	if (nspoid == InvalidOid)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_SCHEMA_NAME),
+				 errmsg("schema %s does not exist", nspname)));
+		return InvalidOid;
+	}
+
+	aclresult = object_aclcheck(NamespaceRelationId, nspoid, GetUserId(), ACL_USAGE);
+
+	if (aclresult != ACLCHECK_OK)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for schema %s", nspname)));
+		return InvalidOid;
+	}
+
+	return nspoid;
+}
+
+
 /*
  * Find the argument number for the given argument name, returning -1 if not
  * found.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c371570501a..bd857bb076c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10490,7 +10490,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 	PQExpBuffer out;
 	DumpId	   *deps = NULL;
 	int			ndeps = 0;
-	char	   *qualified_name;
 	int			i_attname;
 	int			i_inherited;
 	int			i_null_frac;
@@ -10555,15 +10554,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 
 	out = createPQExpBuffer();
 
-	qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
 	/* restore relation stats */
 	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
 	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'relation', ");
-	appendStringLiteralAH(out, qualified_name, fout);
-	appendPQExpBufferStr(out, "::regclass,\n");
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n");
+	appendPQExpBufferStr(out, "\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n");
 	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
 	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
 	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
@@ -10602,9 +10602,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
 		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'relation', ");
-		appendStringLiteralAH(out, qualified_name, fout);
-		appendPQExpBufferStr(out, "::regclass");
+		appendPQExpBufferStr(out, "\t'schemaname', ");
+		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(out, ",\n\t'relname', ");
+		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10616,7 +10617,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 * their attnames are not necessarily stable across dump/reload.
 		 */
 		if (rsinfo->nindAttNames == 0)
-			appendNamedArgument(out, fout, "attname", "name", attname);
+		{
+			appendPQExpBuffer(out, ",\n\t'attname', ");
+			appendStringLiteralAH(out, attname, fout);
+		}
 		else
 		{
 			bool		found = false;
@@ -10696,7 +10700,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							  .deps = deps,
 							  .nDeps = ndeps));
 
-	free(qualified_name);
 	destroyPQExpBuffer(out);
 	destroyPQExpBuffer(query);
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c7bffc1b045..b037f239136 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4725,14 +4725,16 @@ my %tests = (
 		regexp => qr/^
 			\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-			'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+			'schemaname',\s'dump_test',\s+
+			'relname',\s'dup_test_post_data_ix',\s+
 			'relpages',\s'\d+'::integer,\s+
 			'reltuples',\s'\d+'::real,\s+
 			'relallvisible',\s'\d+'::integer\s+
 			\);\s+
 			\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-			'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+			'schemaname',\s'dump_test',\s+
+			'relname',\s'dup_test_post_data_ix',\s+
 			'attnum',\s'2'::smallint,\s+
 			'inherited',\s'f'::boolean,\s+
 			'null_frac',\s'0'::real,\s+
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f46d5e7854..2f1295f2149 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -14,7 +14,8 @@ CREATE TABLE stats_import.test(
 ) WITH (autovacuum_enabled = false);
 SELECT
     pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 18::integer,
         'reltuples', 21::real,
         'relallvisible', 24::integer,
@@ -36,7 +37,7 @@ ORDER BY relname;
  test    |       18 |        21 |            24 |           27
 (1 row)
 
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
  pg_clear_relation_stats 
 -------------------------
  
@@ -45,33 +46,54 @@ SELECT pg_clear_relation_stats('stats_import.test'::regclass);
 --
 -- relstats tests
 --
---- error: relation is wrong type
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid,
+        'relname', 'test',
         'relpages', 17::integer);
-WARNING:  argument "relation" has type "oid", expected type "regclass"
-ERROR:  "relation" cannot be NULL
+ERROR:  "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relpages', 17::integer);
+ERROR:  "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 3.6::float,
+        'relname', 'test',
+        'relpages', 17::integer);
+WARNING:  argument "schemaname" has type "double precision", expected type "text"
+ERROR:  "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relname', 0::oid,
+        'relpages', 17::integer);
+WARNING:  argument "relname" has type "oid", expected type "text"
+ERROR:  "relname" cannot be NULL
 -- error: relation not found
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'nope',
         'relpages', 17::integer);
-ERROR:  could not open relation with OID 0
+WARNING:  Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- error: odd number of variadic arguments cannot be pairs
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible');
 ERROR:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
 -- error: argument name is NULL
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
-        'relation', '0'::oid::regclass,
-        17, '17'::integer);
-ERROR:  name at variadic position 3 has type "integer", expected type "text"
+ERROR:  name at variadic position 5 is NULL
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -84,7 +106,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
 -- regular indexes have special case locking rules
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test_i',
         'relpages', 18::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -132,7 +155,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
 --
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.part_parent_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'part_parent_i',
         'relpages', 2::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -166,7 +190,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
 
 -- ok: set all relstats, with version, no bounds checking
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relpages', '-17'::integer,
         'reltuples', 400::real,
@@ -187,7 +212,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relpages, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '16'::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -204,7 +230,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just reltuples, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'reltuples', '500'::real);
  pg_restore_relation_stats 
 ---------------------------
@@ -221,7 +248,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relallvisible, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible', 5::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -238,7 +266,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: just relallfrozen
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relallfrozen', 3::integer);
  pg_restore_relation_stats 
@@ -256,7 +285,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- warn: bad relpages type, rest updated
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 'nope'::text,
         'reltuples', 400.0::real,
         'relallvisible', 4::integer,
@@ -277,7 +307,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- unrecognized argument name, rest ok
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '171'::integer,
         'nope', 10::integer);
 WARNING:  unrecognized argument name: "nope"
@@ -295,8 +326,7 @@ WHERE oid = 'stats_import.test'::regclass;
 (1 row)
 
 -- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
-    relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
  pg_clear_relation_stats 
 -------------------------
  
@@ -313,87 +343,123 @@ WHERE oid = 'stats_import.test'::regclass;
 -- invalid relkinds for statistics
 CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testseq'::regclass);
+        'schemaname', 'stats_import',
+        'relname', 'testseq');
 ERROR:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
 ERROR:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testview'::regclass);
-ERROR:  cannot modify statistics for relation "testview"
-DETAIL:  This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
 ERROR:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
 --
 -- attribute stats
 --
--- error: object does not exist
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', '0'::oid::regclass,
-    'attname', 'id'::name,
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  could not open relation with OID 0
--- error: relation null
+ERROR:  "schemaname" cannot be NULL
+-- error: schema does not exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', NULL::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'nope',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relation" cannot be NULL
+WARNING:  schema nope does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+ERROR:  "relname" cannot be NULL
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'nope',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+WARNING:  Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', NULL,
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+ERROR:  "relname" cannot be NULL
 -- error: NULL attname
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', NULL::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  must specify either attname or attnum
 -- error: attname doesn't exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'nope'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'nope',
     'inherited', false::boolean,
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "test" does not exist
+ERROR:  column "nope" of relation "stats_import"."test" does not exist
 -- error: both attname and attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  cannot specify both attname and attnum
 -- error: neither attname nor attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  must specify either attname or attnum
 -- error: attribute is system column
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'xmin'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  cannot modify statistics on system column "xmin"
 -- error: inherited null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
 ERROR:  "inherited" cannot be NULL
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'version', 150000::integer,
     'null_frac', 0.2::real,
@@ -421,7 +487,8 @@ AND attname = 'id';
 -- for any stat-having relation.
 --
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.4::real);
@@ -443,8 +510,9 @@ AND attname = 'id';
 
 -- warn: unrecognized argument name, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.2::real,
     'nope', 0.5::real);
@@ -467,8 +535,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 1, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -492,8 +561,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 2, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_vals', '{1,2,3}'::text
@@ -517,8 +587,9 @@ AND attname = 'id';
 
 -- warn: mcf type mismatch, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.22::real,
     'most_common_vals', '{2,1,3}'::text,
@@ -544,8 +615,9 @@ AND attname = 'id';
 
 -- warn: mcv cast failure, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.23::real,
     'most_common_vals', '{2,four,3}'::text,
@@ -570,8 +642,9 @@ AND attname = 'id';
 
 -- ok: mcv+mcf
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'most_common_vals', '{2,1,3}'::text,
     'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -594,8 +667,9 @@ AND attname = 'id';
 
 -- warn: NULL in histogram array, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.24::real,
     'histogram_bounds', '{1,NULL,3,4}'::text
@@ -619,8 +693,9 @@ AND attname = 'id';
 
 -- ok: histogram_bounds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'histogram_bounds', '{1,2,3,4}'::text
     );
@@ -642,8 +717,9 @@ AND attname = 'id';
 
 -- warn: elem_count_histogram null element, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.25::real,
     'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -667,8 +743,9 @@ AND attname = 'tags';
 
 -- ok: elem_count_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.26::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -691,8 +768,9 @@ AND attname = 'tags';
 
 -- warn: range stats on a scalar type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.27::real,
     'range_empty_frac', 0.5::real,
@@ -718,8 +796,9 @@ AND attname = 'id';
 
 -- warn: range_empty_frac range_length_hist null mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.28::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -743,8 +822,9 @@ AND attname = 'arange';
 
 -- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.29::real,
     'range_empty_frac', 0.5::real
@@ -768,8 +848,9 @@ AND attname = 'arange';
 
 -- ok: range_empty_frac + range_length_hist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_empty_frac', 0.5::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -792,8 +873,9 @@ AND attname = 'arange';
 
 -- warn: range bounds histogram on scalar, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.31::real,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -818,8 +900,9 @@ AND attname = 'id';
 
 -- ok: range_bounds_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
     );
@@ -841,8 +924,9 @@ AND attname = 'arange';
 
 -- warn: cannot set most_common_elems for range type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.32::real,
     'most_common_elems', '{3,1}'::text,
@@ -868,8 +952,9 @@ AND attname = 'arange';
 
 -- warn: scalars can't have mcelem, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.33::real,
     'most_common_elems', '{1,3}'::text,
@@ -895,8 +980,9 @@ AND attname = 'id';
 
 -- warn: mcelem / mcelem mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.34::real,
     'most_common_elems', '{one,two}'::text
@@ -920,8 +1006,9 @@ AND attname = 'tags';
 
 -- warn: mcelem / mcelem null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.35::real,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -945,8 +1032,9 @@ AND attname = 'tags';
 
 -- ok: mcelem
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'most_common_elems', '{one,three}'::text,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -969,8 +1057,9 @@ AND attname = 'tags';
 
 -- warn: scalars can't have elem_count_histogram, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.36::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -1022,8 +1111,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
 FROM pg_catalog.pg_stats AS s
 CROSS JOIN LATERAL
     pg_catalog.pg_restore_attribute_stats(
-        'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
-        'attname', s.attname,
+        'schemaname', 'stats_import',
+        'relname', s.tablename::text || '_clone',
+        'attname', s.attname::text,
         'inherited', s.inherited,
         'version', 150000,
         'null_frac', s.null_frac,
@@ -1200,9 +1290,10 @@ AND attname = 'arange';
 (1 row)
 
 SELECT pg_catalog.pg_clear_attribute_stats(
-    relation => 'stats_import.test'::regclass,
-    attname => 'arange'::name,
-    inherited => false::boolean);
+    schemaname => 'stats_import',
+    relname => 'test',
+    attname => 'arange',
+    inherited => false);
  pg_clear_attribute_stats 
 --------------------------
  
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 0ec590688c2..ccdc44e9236 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,7 +17,8 @@ CREATE TABLE stats_import.test(
 
 SELECT
     pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 18::integer,
         'reltuples', 21::real,
         'relallvisible', 24::integer,
@@ -32,37 +33,52 @@ FROM pg_class
 WHERE oid = 'stats_import.test'::regclass
 ORDER BY relname;
 
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
 
 --
 -- relstats tests
 --
 
---- error: relation is wrong type
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid,
+        'relname', 'test',
+        'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 3.6::float,
+        'relname', 'test',
+        'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relname', 0::oid,
         'relpages', 17::integer);
 
 -- error: relation not found
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'nope',
         'relpages', 17::integer);
 
 -- error: odd number of variadic arguments cannot be pairs
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible');
 
 -- error: argument name is NULL
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         NULL, '17'::integer);
 
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
-        'relation', '0'::oid::regclass,
-        17, '17'::integer);
-
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -71,7 +87,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
 -- regular indexes have special case locking rules
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test_i',
         'relpages', 18::integer);
 
 SELECT mode FROM pg_locks
@@ -108,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
 BEGIN;
 
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.part_parent_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'part_parent_i',
         'relpages', 2::integer);
 
 SELECT mode FROM pg_locks
@@ -127,7 +145,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
 
 -- ok: set all relstats, with version, no bounds checking
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relpages', '-17'::integer,
         'reltuples', 400::real,
@@ -140,7 +159,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relpages, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '16'::integer);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -149,7 +169,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just reltuples, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'reltuples', '500'::real);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -158,7 +179,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relallvisible, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible', 5::integer);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,7 +189,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: just relallfrozen
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relallfrozen', 3::integer);
 
@@ -177,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- warn: bad relpages type, rest updated
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 'nope'::text,
         'reltuples', 400.0::real,
         'relallvisible', 4::integer,
@@ -189,7 +213,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- unrecognized argument name, rest ok
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '171'::integer,
         'nope', 10::integer);
 
@@ -198,8 +223,7 @@ FROM pg_class
 WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
-    relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
 
 SELECT relpages, reltuples, relallvisible
 FROM pg_class
@@ -209,48 +233,70 @@ WHERE oid = 'stats_import.test'::regclass;
 CREATE SEQUENCE stats_import.testseq;
 
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testseq'::regclass);
+        'schemaname', 'stats_import',
+        'relname', 'testseq');
 
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
 
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 
-SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
 
 --
 -- attribute stats
 --
 
--- error: object does not exist
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', '0'::oid::regclass,
-    'attname', 'id'::name,
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relation null
+-- error: schema does not exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', NULL::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'nope',
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'nope',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', NULL,
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: NULL attname
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', NULL::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: attname doesn't exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'nope'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'nope',
     'inherited', false::boolean,
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
@@ -258,36 +304,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
 
 -- error: both attname and attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: neither attname nor attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: attribute is system column
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'xmin'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: inherited null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
 
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'version', 150000::integer,
     'null_frac', 0.2::real,
@@ -307,7 +358,8 @@ AND attname = 'id';
 -- for any stat-having relation.
 --
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.4::real);
@@ -321,8 +373,9 @@ AND attname = 'id';
 
 -- warn: unrecognized argument name, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.2::real,
     'nope', 0.5::real);
@@ -336,8 +389,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 1, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -352,8 +406,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 2, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_vals', '{1,2,3}'::text
@@ -368,8 +423,9 @@ AND attname = 'id';
 
 -- warn: mcf type mismatch, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.22::real,
     'most_common_vals', '{2,1,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
 
 -- warn: mcv cast failure, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.23::real,
     'most_common_vals', '{2,four,3}'::text,
@@ -402,8 +459,9 @@ AND attname = 'id';
 
 -- ok: mcv+mcf
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'most_common_vals', '{2,1,3}'::text,
     'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -418,8 +476,9 @@ AND attname = 'id';
 
 -- warn: NULL in histogram array, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.24::real,
     'histogram_bounds', '{1,NULL,3,4}'::text
@@ -434,8 +493,9 @@ AND attname = 'id';
 
 -- ok: histogram_bounds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'histogram_bounds', '{1,2,3,4}'::text
     );
@@ -449,8 +509,9 @@ AND attname = 'id';
 
 -- warn: elem_count_histogram null element, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.25::real,
     'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -465,8 +526,9 @@ AND attname = 'tags';
 
 -- ok: elem_count_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.26::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -481,8 +543,9 @@ AND attname = 'tags';
 
 -- warn: range stats on a scalar type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.27::real,
     'range_empty_frac', 0.5::real,
@@ -498,8 +561,9 @@ AND attname = 'id';
 
 -- warn: range_empty_frac range_length_hist null mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.28::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -514,8 +578,9 @@ AND attname = 'arange';
 
 -- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.29::real,
     'range_empty_frac', 0.5::real
@@ -530,8 +595,9 @@ AND attname = 'arange';
 
 -- ok: range_empty_frac + range_length_hist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_empty_frac', 0.5::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -546,8 +612,9 @@ AND attname = 'arange';
 
 -- warn: range bounds histogram on scalar, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.31::real,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -562,8 +629,9 @@ AND attname = 'id';
 
 -- ok: range_bounds_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
     );
@@ -577,8 +645,9 @@ AND attname = 'arange';
 
 -- warn: cannot set most_common_elems for range type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.32::real,
     'most_common_elems', '{3,1}'::text,
@@ -594,8 +663,9 @@ AND attname = 'arange';
 
 -- warn: scalars can't have mcelem, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.33::real,
     'most_common_elems', '{1,3}'::text,
@@ -611,8 +681,9 @@ AND attname = 'id';
 
 -- warn: mcelem / mcelem mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.34::real,
     'most_common_elems', '{one,two}'::text
@@ -627,8 +698,9 @@ AND attname = 'tags';
 
 -- warn: mcelem / mcelem null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.35::real,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -643,8 +715,9 @@ AND attname = 'tags';
 
 -- ok: mcelem
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'most_common_elems', '{one,three}'::text,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -659,8 +732,9 @@ AND attname = 'tags';
 
 -- warn: scalars can't have elem_count_histogram, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.36::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -707,8 +781,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
 FROM pg_catalog.pg_stats AS s
 CROSS JOIN LATERAL
     pg_catalog.pg_restore_attribute_stats(
-        'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
-        'attname', s.attname,
+        'schemaname', 'stats_import',
+        'relname', s.tablename::text || '_clone',
+        'attname', s.attname::text,
         'inherited', s.inherited,
         'version', 150000,
         'null_frac', s.null_frac,
@@ -853,9 +928,10 @@ AND inherited = false
 AND attname = 'arange';
 
 SELECT pg_catalog.pg_clear_attribute_stats(
-    relation => 'stats_import.test'::regclass,
-    attname => 'arange'::name,
-    inherited => false::boolean);
+    schemaname => 'stats_import',
+    relname => 'test',
+    attname => 'arange',
+    inherited => false);
 
 SELECT COUNT(*)
 FROM pg_stats
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1c3810e1a04..a75e95bc5fd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30365,22 +30365,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <structname>mytable</structname>:
 <programlisting>
  SELECT pg_restore_relation_stats(
-    'relation',  'mytable'::regclass,
-    'relpages',  173::integer,
-    'reltuples', 10000::real);
+    'schemaname', 'myschema',
+    'relname',    'mytable',
+    'relpages',   173::integer,
+    'reltuples',  10000::real);
 </programlisting>
         </para>
         <para>
-         The argument <literal>relation</literal> with a value of type
-         <type>regclass</type> is required, and specifies the table. Other
-         arguments are the names and values of statistics corresponding to
-         certain columns in <link
+         The arguments <literal>schemaname</literal> with a value of type
+         <type>regclass</type> and <literal>relname</literal> are required,
+         and specifies the table. Other arguments are the names and values
+         of statistics corresponding to certain columns in <link
          linkend="catalog-pg-class"><structname>pg_class</structname></link>.
          The currently-supported relation statistics are
          <literal>relpages</literal> with a value of type
          <type>integer</type>, <literal>reltuples</literal> with a value of
-         type <type>real</type>, and <literal>relallvisible</literal> with a
-         value of type <type>integer</type>.
+         type <type>real</type>, <literal>relallvisible</literal> with a
+         value of type <type>integer</type>, and <literal>relallfrozen</literal>
+         with a value of type <type>integer</type>.
         </para>
         <para>
          Additionally, this function accepts argument name
@@ -30408,7 +30410,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <indexterm>
           <primary>pg_clear_relation_stats</primary>
          </indexterm>
-         <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+         <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
          <returnvalue>void</returnvalue>
         </para>
         <para>
@@ -30457,16 +30459,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <structname>mytable</structname>:
 <programlisting>
  SELECT pg_restore_attribute_stats(
-    'relation',    'mytable'::regclass,
-    'attname',     'col1'::name,
-    'inherited',   false,
-    'avg_width',   125::integer,
-    'null_frac',   0.5::real);
+    'schemaname', 'myschema',
+    'relname',    'mytable',
+    'attname',    'col1',
+    'inherited',  false,
+    'avg_width',  125::integer,
+    'null_frac',  0.5::real);
 </programlisting>
         </para>
         <para>
-         The required arguments are <literal>relation</literal> with a value
-         of type <type>regclass</type>, which specifies the table; either
+         The required arguments are <literal>schemaname</literal> with a value
+         of type <type>regclass</type> and <literal>relname</literal> with a value
+         of type <type>text</type> which specify the table; either
          <literal>attname</literal> with a value of type <type>name</type> or
          <literal>attnum</literal> with a value of type <type>smallint</type>,
          which specifies the column; and <literal>inherited</literal>, which
@@ -30502,7 +30506,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
           <primary>pg_clear_attribute_stats</primary>
          </indexterm>
          <function>pg_clear_attribute_stats</function> (
-         <parameter>relation</parameter> <type>regclass</type>,
+         <parameter>schemaname</parameter> <type>text</type>,
+         <parameter>relname</parameter> <type>text</type>,
          <parameter>attname</parameter> <type>name</type>,
          <parameter>inherited</parameter> <type>boolean</type> )
          <returnvalue>void</returnvalue>

base-commit: 6d376c3b0d1e79c318d2a1c04097025784e28377
-- 
2.48.1



  [text/x-patch] v8-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch (29.8K, ../../CADkLM=c+r05srPy9w+-+nbmLEo15dKXYQ03Q_xyK+riJerigLQ@mail.gmail.com/4-v8-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch)
  download | inline diff:
From 98f1eea90be0804ecdd43a89306aa83a6b9784c5 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v8 2/4] Downgrade as man pg_restore_*_stats errors to
 warnings.

We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
 src/include/statistics/stat_utils.h        |   4 +-
 src/backend/statistics/attribute_stats.c   | 124 +++++++++++-----
 src/backend/statistics/relation_stats.c    |  10 +-
 src/backend/statistics/stat_utils.c        |  51 +++++--
 src/test/regress/expected/stats_import.out | 163 ++++++++++++++++-----
 src/test/regress/sql/stats_import.sql      |  36 ++---
 6 files changed, 277 insertions(+), 111 deletions(-)

diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index cad042c8e4a..298cbae3436 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
 	Oid			argtype;
 };
 
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
 									 struct StatsArgInfo *arginfo,
 									 int argnum);
 extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 								 struct StatsArgInfo *arginfo,
 								 int argnum1, int argnum2);
 
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
 
 extern Oid stats_schema_check_privileges(const char *nspname);
 
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f87db2d6102..4f9bc18f8c6 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
 
 static bool attribute_statistics_update(FunctionCallInfo fcinfo);
 static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
 							   Oid *atttypid, int32 *atttypmod,
 							   char *atttyptype, Oid *atttypcoll,
 							   Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
  * stored as an anyarray, and the representation of the array needs to store
  * the correct element type, which must be derived from the attribute.
  *
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
  */
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -149,8 +151,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	HeapTuple	statup;
 
 	Oid			atttypid = InvalidOid;
-	int32		atttypmod;
-	char		atttyptype;
+	int32		atttypmod = -1;
+	char		atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
 	Oid			atttypcoll = InvalidOid;
 	Oid			eq_opr = InvalidOid;
 	Oid			lt_opr = InvalidOid;
@@ -177,17 +179,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+		return false;
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
-	if (nspoid == InvalidOid)
+	if (!OidIsValid(nspoid))
 		return false;
 
 	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
 	reloid = get_relname_relid(relname, nspoid);
-	if (reloid == InvalidOid)
+	if (!OidIsValid(reloid))
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -196,29 +200,39 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		return false;
+	}
 
 	/* lock before looking up attribute */
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
 		if (!PG_ARGISNULL(ATTNUM_ARG))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
+			return false;
+		}
 		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
 							attname, nspname, relname)));
+			return false;
+		}
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -227,27 +241,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		/* annoyingly, get_attname doesn't check attisdropped */
 		if (attname == NULL ||
 			!SearchSysCacheExistsAttName(reloid, attname))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column %d of relation \"%s\".\"%s\" does not exist",
 							attnum, nspname, relname)));
+			return false;
+		}
 	}
 	else
 	{
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("must specify either attname or attnum")));
-		attname = NULL;			/* keep compiler quiet */
-		attnum = 0;
+		return false;
 	}
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
+		return false;
+	}
 
-	stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+		return false;
 	inherited = PG_GETARG_BOOL(INHERITED_ARG);
 
 	/*
@@ -296,10 +316,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* derive information from attribute */
-	get_attr_stat_type(reloid, attnum,
-					   &atttypid, &atttypmod,
-					   &atttyptype, &atttypcoll,
-					   &eq_opr, &lt_opr);
+	if (!get_attr_stat_type(reloid, attnum,
+							&atttypid, &atttypmod,
+							&atttyptype, &atttypcoll,
+							&eq_opr, &lt_opr))
+		result = false;
 
 	/* if needed, derive element type */
 	if (do_mcelem || do_dechist)
@@ -579,7 +600,7 @@ get_attr_expr(Relation rel, int attnum)
 /*
  * Derive type information from the attribute.
  */
-static void
+static bool
 get_attr_stat_type(Oid reloid, AttrNumber attnum,
 				   Oid *atttypid, int32 *atttypmod,
 				   char *atttyptype, Oid *atttypcoll,
@@ -596,18 +617,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 
 	/* Attribute not found */
 	if (!HeapTupleIsValid(atup))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	attr = (Form_pg_attribute) GETSTRUCT(atup);
 
 	if (attr->attisdropped)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	expr = get_attr_expr(rel, attr->attnum);
 
@@ -656,6 +685,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 		*atttypcoll = DEFAULT_COLLATION_OID;
 
 	relation_close(rel, NoLock);
+	return true;
 }
 
 /*
@@ -781,6 +811,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
 	if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
 		slotidx = first_empty;
 
+	/*
+	 * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+	 * statistic kinds, so this can safely remain an ERROR for now.
+	 */
 	if (slotidx >= STATISTIC_NUM_SLOTS)
 		ereport(ERROR,
 				(errmsg("maximum number of statistics slots exceeded: %d",
@@ -927,15 +961,19 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+		PG_RETURN_VOID();
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
 	if (!OidIsValid(nspoid))
-		return false;
+		PG_RETURN_VOID();
 
 	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
 	reloid = get_relname_relid(relname, nspoid);
@@ -944,31 +982,41 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
-		return false;
+		PG_RETURN_VOID();
 	}
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		PG_RETURN_VOID();
+	}
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		PG_RETURN_VOID();
 
 	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
 	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
 						attname)));
+		PG_RETURN_VOID();
+	}
 
 	if (attnum == InvalidAttrNumber)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
 						attname, get_rel_name(reloid))));
+		PG_RETURN_VOID();
+	}
 
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index fdc69bc93e2..49109cf721d 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -84,8 +84,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
-	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+		return false;
+
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
@@ -108,7 +111,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e037d4994e8..dd9d88ac1c5 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -34,16 +34,20 @@
 /*
  * Ensure that a given argument is not null.
  */
-void
+bool
 stats_check_required_arg(FunctionCallInfo fcinfo,
 						 struct StatsArgInfo *arginfo,
 						 int argnum)
 {
 	if (PG_ARGISNULL(argnum))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("\"%s\" cannot be NULL",
 						arginfo[argnum].argname)));
+		return false;
+	}
+	return true;
 }
 
 /*
@@ -128,13 +132,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
  *   - the role owns the current database and the relation is not shared
  *   - the role has the MAINTAIN privilege on the relation
  */
-void
+bool
 stats_lock_check_privileges(Oid reloid)
 {
 	Relation	table;
 	Oid			table_oid = reloid;
 	Oid			index_oid = InvalidOid;
 	LOCKMODE	index_lockmode = NoLock;
+	bool		ok = true;
 
 	/*
 	 * For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -174,14 +179,15 @@ stats_lock_check_privileges(Oid reloid)
 		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
-			ereport(ERROR,
+			ereport(WARNING,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot modify statistics for relation \"%s\"",
 							RelationGetRelationName(table)),
 					 errdetail_relkind_not_supported(table->rd_rel->relkind)));
+		ok = false;
 	}
 
-	if (OidIsValid(index_oid))
+	if (ok && (OidIsValid(index_oid)))
 	{
 		Relation	index;
 
@@ -194,25 +200,33 @@ stats_lock_check_privileges(Oid reloid)
 		relation_close(index, NoLock);
 	}
 
-	if (table->rd_rel->relisshared)
-		ereport(ERROR,
+	if (ok && (table->rd_rel->relisshared))
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics for shared relation")));
+		ok = false;
+	}
 
-	if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+	if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
 	{
 		AclResult	aclresult = pg_class_aclcheck(RelationGetRelid(table),
 												  GetUserId(),
 												  ACL_MAINTAIN);
 
 		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult,
-						   get_relkind_objtype(table->rd_rel->relkind),
-						   NameStr(table->rd_rel->relname));
+		{
+			ereport(WARNING,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						errmsg("permission denied for relation %s",
+							   NameStr(table->rd_rel->relname))));
+			ok = false;
+		}
 	}
 
 	/* retain lock on table */
 	relation_close(table, NoLock);
+	return ok;
 }
 
 
@@ -318,9 +332,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 								  &args, &types, &argnulls);
 
 	if (nargs % 2 != 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				errmsg("variadic arguments must be name/value pairs"),
 				errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+		return false;
+	}
 
 	/*
 	 * For each argument name/value pair, find corresponding positional
@@ -333,14 +350,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 		char	   *argname;
 
 		if (argnulls[i])
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d is NULL", i + 1)));
+			return false;
+		}
 
 		if (types[i] != TEXTOID)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
 							i + 1, format_type_be(types[i]),
 							format_type_be(TEXTOID))));
+			return false;
+		}
 
 		if (argnulls[i + 1])
 			continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 2f1295f2149..6551d6bf099 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,31 +46,51 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 --
 -- relstats tests
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
-ERROR:  "schemaname" cannot be NULL
--- error: relname missing
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
-ERROR:  "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 WARNING:  argument "schemaname" has type "double precision", expected type "text"
-ERROR:  "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 WARNING:  argument "relname" has type "oid", expected type "text"
-ERROR:  "relname" cannot be NULL
--- error: relation not found
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
@@ -81,19 +101,30 @@ WARNING:  Relation "stats_import"."nope" not found.
  f
 (1 row)
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
-ERROR:  variadic arguments must be name/value pairs
+WARNING:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 5 is NULL
+WARNING:  name at variadic position 5 is NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -345,26 +376,46 @@ CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR:  cannot modify statistics for relation "testview"
+WARNING:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 --
 -- attribute stats
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -377,14 +428,19 @@ WARNING:  schema nope does not exist
  f
 (1 row)
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: relname does not exist
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -397,23 +453,33 @@ WARNING:  Relation "stats_import"."nope" not found.
  f
 (1 row)
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: NULL attname
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attname doesn't exist
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -422,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "stats_import"."test" does not exist
--- error: both attname and attnum
+WARNING:  column "nope" of relation "stats_import"."test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -431,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING:  cannot specify both attname and attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attribute is system column
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING:  cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
-ERROR:  "inherited" cannot be NULL
+WARNING:  "inherited" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index ccdc44e9236..dbbebce1673 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 -- relstats tests
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
 
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 
--- error: relation not found
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
 
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
 -- attribute stats
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: NULL attname
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
 
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: inherited null
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
-- 
2.48.1



  [text/x-patch] v8-0003-Introduce-CreateStmtPtr.patch (17.2K, ../../CADkLM=c+r05srPy9w+-+nbmLEo15dKXYQ03Q_xyK+riJerigLQ@mail.gmail.com/5-v8-0003-Introduce-CreateStmtPtr.patch)
  download | inline diff:
From e2b00e46afdfa45f263b4666831160bf4d21dd09 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v8 3/4] Introduce CreateStmtPtr.

CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.

Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
 src/bin/pg_dump/pg_backup.h          |   2 +
 src/bin/pg_dump/pg_backup_archiver.c |  22 ++-
 src/bin/pg_dump/pg_backup_archiver.h |   7 +
 src/bin/pg_dump/pg_dump.c            | 230 +++++++++++++++------------
 4 files changed, 156 insertions(+), 105 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index e783cc68d89..bb175874a5a 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -287,6 +287,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
 
 typedef void (*SetupWorkerPtrType) (Archive *AH);
 
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
 /*
  * Main archiver interface.
  */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 7480e122b61..3fcfecf6719 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1263,6 +1263,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
 	newToc->dataDumper = opts->dumpFn;
 	newToc->dataDumperArg = opts->dumpArg;
 	newToc->hadDumper = opts->dumpFn ? true : false;
+	newToc->createDumper = opts->createFn;
+	newToc->createDumperArg = opts->createArg;
+	newToc->hadCreateDumper = opts->createFn ? true : false;
 
 	newToc->formatData = NULL;
 	newToc->dataLength = 0;
@@ -2619,7 +2622,17 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->tag);
 		WriteStr(AH, te->desc);
 		WriteInt(AH, te->section);
-		WriteStr(AH, te->defn);
+
+		if (te->hadCreateDumper)
+		{
+			char	   *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+			WriteStr(AH, defn);
+			pg_free(defn);
+		}
+		else
+			WriteStr(AH, te->defn);
+
 		WriteStr(AH, te->dropStmt);
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
@@ -3849,6 +3862,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
 	{
 		IssueACLPerBlob(AH, te);
 	}
+	else if (te->hadCreateDumper)
+	{
+		char	   *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+		ahwrite(ptr, 1, strlen(ptr), AH);
+		pg_free(ptr);
+	}
 	else if (te->defn && strlen(te->defn) > 0)
 	{
 		ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
 	const void *dataDumperArg;	/* Arg for above routine */
 	void	   *formatData;		/* TOC Entry data specific to file format */
 
+	CreateStmtPtr createDumper; /* Routine for create statement creation */
+	const void *createDumperArg;	/* arg for the above routine */
+	bool		hadCreateDumper;	/* Archiver was passed a create statement
+									 * routine */
+
 	/* working state while dumping/restoring */
 	pgoff_t		dataLength;		/* item's data size; 0 if none or unknown */
 	int			reqs;			/* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
 	int			nDeps;
 	DataDumperPtr dumpFn;
 	const void *dumpArg;
+	CreateStmtPtr createFn;
+	const void *createArg;
 } ArchiveOpts;
 #define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
 /* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bd857bb076c..38ba6a90106 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10477,51 +10477,44 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
 }
 
 /*
- * dumpRelationStats --
+ * printDumpRelationStats --
  *
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
  */
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
 {
+	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
 	const DumpableObject *dobj = &rsinfo->dobj;
+
+	PQExpBufferData query;
+	PQExpBufferData out;
+
 	PGresult   *res;
-	PQExpBuffer query;
-	PQExpBuffer out;
-	DumpId	   *deps = NULL;
-	int			ndeps = 0;
-	int			i_attname;
-	int			i_inherited;
-	int			i_null_frac;
-	int			i_avg_width;
-	int			i_n_distinct;
-	int			i_most_common_vals;
-	int			i_most_common_freqs;
-	int			i_histogram_bounds;
-	int			i_correlation;
-	int			i_most_common_elems;
-	int			i_most_common_elem_freqs;
-	int			i_elem_count_histogram;
-	int			i_range_length_histogram;
-	int			i_range_empty_frac;
-	int			i_range_bounds_histogram;
 
-	/* nothing to do if we are not dumping statistics */
-	if (!fout->dopt->dumpStatistics)
-		return;
+	static bool first_query = true;
+	static int	i_attname;
+	static int	i_inherited;
+	static int	i_null_frac;
+	static int	i_avg_width;
+	static int	i_n_distinct;
+	static int	i_most_common_vals;
+	static int	i_most_common_freqs;
+	static int	i_histogram_bounds;
+	static int	i_correlation;
+	static int	i_most_common_elems;
+	static int	i_most_common_elem_freqs;
+	static int	i_elem_count_histogram;
+	static int	i_range_length_histogram;
+	static int	i_range_empty_frac;
+	static int	i_range_bounds_histogram;
 
-	/* dependent on the relation definition, if doing schema */
-	if (fout->dopt->dumpSchema)
+	initPQExpBuffer(&query);
+
+	if (first_query)
 	{
-		deps = dobj->dependencies;
-		ndeps = dobj->nDeps;
-	}
-
-	query = createPQExpBuffer();
-	if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
-	{
-		appendPQExpBufferStr(query,
-							 "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+		appendPQExpBufferStr(&query,
+							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
 							 "SELECT s.attname, s.inherited, "
 							 "s.null_frac, s.avg_width, s.n_distinct, "
 							 "s.most_common_vals, s.most_common_freqs, "
@@ -10530,82 +10523,85 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							 "s.elem_count_histogram, ");
 
 		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "s.range_length_histogram, "
 								 "s.range_empty_frac, "
 								 "s.range_bounds_histogram ");
 		else
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "NULL AS range_length_histogram,"
 								 "NULL AS range_empty_frac,"
 								 "NULL AS range_bounds_histogram ");
 
-		appendPQExpBufferStr(query,
+		appendPQExpBufferStr(&query,
 							 "FROM pg_catalog.pg_stats s "
 							 "WHERE s.schemaname = $1 "
 							 "AND s.tablename = $2 "
 							 "ORDER BY s.attname, s.inherited");
 
-		ExecuteSqlStatement(fout, query->data);
+		ExecuteSqlStatement(fout, query.data);
 
-		fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
-		resetPQExpBuffer(query);
+		resetPQExpBuffer(&query);
 	}
 
-	out = createPQExpBuffer();
+	initPQExpBuffer(&out);
 
 	/* restore relation stats */
-	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
+	appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'schemaname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBufferStr(out, "\t'relname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+	appendPQExpBufferStr(&out, "\t'schemaname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(&out, ",\n");
+	appendPQExpBufferStr(&out, "\t'relname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+	appendPQExpBufferStr(&out, ",\n");
+	appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+	appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+	appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
 					  rsinfo->relallvisible);
 
 	/* fetch attribute stats */
-	appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(query, ", ");
-	appendStringLiteralAH(query, dobj->name, fout);
-	appendPQExpBufferStr(query, ");");
+	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+	appendPQExpBufferStr(&query, ", ");
+	appendStringLiteralAH(&query, dobj->name, fout);
+	appendPQExpBufferStr(&query, ")");
 
-	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
 
-	i_attname = PQfnumber(res, "attname");
-	i_inherited = PQfnumber(res, "inherited");
-	i_null_frac = PQfnumber(res, "null_frac");
-	i_avg_width = PQfnumber(res, "avg_width");
-	i_n_distinct = PQfnumber(res, "n_distinct");
-	i_most_common_vals = PQfnumber(res, "most_common_vals");
-	i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-	i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-	i_correlation = PQfnumber(res, "correlation");
-	i_most_common_elems = PQfnumber(res, "most_common_elems");
-	i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-	i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-	i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-	i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-	i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+	if (first_query)
+	{
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		first_query = false;
+	}
 
 	/* restore attribute stats */
 	for (int rownum = 0; rownum < PQntuples(res); rownum++)
 	{
 		const char *attname;
 
-		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'schemaname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(out, ",\n\t'relname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+		appendPQExpBufferStr(&out, "\t'schemaname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(&out, ",\n\t'relname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10618,8 +10614,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 */
 		if (rsinfo->nindAttNames == 0)
 		{
-			appendPQExpBuffer(out, ",\n\t'attname', ");
-			appendStringLiteralAH(out, attname, fout);
+			appendPQExpBuffer(&out, ",\n\t'attname', ");
+			appendStringLiteralAH(&out, attname, fout);
 		}
 		else
 		{
@@ -10629,7 +10625,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 			{
 				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
 				{
-					appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
 									  i + 1);
 					found = true;
 					break;
@@ -10641,67 +10637,93 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		}
 
 		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(out, fout, "inherited", "boolean",
+			appendNamedArgument(&out, fout, "inherited", "boolean",
 								PQgetvalue(res, rownum, i_inherited));
 		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(out, fout, "null_frac", "real",
+			appendNamedArgument(&out, fout, "null_frac", "real",
 								PQgetvalue(res, rownum, i_null_frac));
 		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(out, fout, "avg_width", "integer",
+			appendNamedArgument(&out, fout, "avg_width", "integer",
 								PQgetvalue(res, rownum, i_avg_width));
 		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(out, fout, "n_distinct", "real",
+			appendNamedArgument(&out, fout, "n_distinct", "real",
 								PQgetvalue(res, rownum, i_n_distinct));
 		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(out, fout, "most_common_vals", "text",
+			appendNamedArgument(&out, fout, "most_common_vals", "text",
 								PQgetvalue(res, rownum, i_most_common_vals));
 		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_freqs));
 		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(out, fout, "histogram_bounds", "text",
+			appendNamedArgument(&out, fout, "histogram_bounds", "text",
 								PQgetvalue(res, rownum, i_histogram_bounds));
 		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(out, fout, "correlation", "real",
+			appendNamedArgument(&out, fout, "correlation", "real",
 								PQgetvalue(res, rownum, i_correlation));
 		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(out, fout, "most_common_elems", "text",
+			appendNamedArgument(&out, fout, "most_common_elems", "text",
 								PQgetvalue(res, rownum, i_most_common_elems));
 		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_elem_freqs));
 		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
 								PQgetvalue(res, rownum, i_elem_count_histogram));
 		if (fout->remoteVersion >= 170000)
 		{
 			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(out, fout, "range_length_histogram", "text",
+				appendNamedArgument(&out, fout, "range_length_histogram", "text",
 									PQgetvalue(res, rownum, i_range_length_histogram));
 			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(out, fout, "range_empty_frac", "real",
+				appendNamedArgument(&out, fout, "range_empty_frac", "real",
 									PQgetvalue(res, rownum, i_range_empty_frac));
 			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
 									PQgetvalue(res, rownum, i_range_bounds_histogram));
 		}
-		appendPQExpBufferStr(out, "\n);\n");
+		appendPQExpBufferStr(&out, "\n);\n");
 	}
 
 	PQclear(res);
 
+	termPQExpBuffer(&query);
+	return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+	const DumpableObject *dobj = &rsinfo->dobj;
+
+	DumpId	   *deps = NULL;
+	int			ndeps = 0;
+
+	/* nothing to do if we are not dumping statistics */
+	if (!fout->dopt->dumpStatistics)
+		return;
+
+	/* dependent on the relation definition, if doing schema */
+	if (fout->dopt->dumpSchema)
+	{
+		deps = dobj->dependencies;
+		ndeps = dobj->nDeps;
+	}
+
 	ArchiveEntry(fout, nilCatalogId, createDumpId(),
 				 ARCHIVE_OPTS(.tag = dobj->name,
 							  .namespace = dobj->namespace->dobj.name,
 							  .description = "STATISTICS DATA",
 							  .section = rsinfo->postponed_def ?
 							  SECTION_POST_DATA : statisticsDumpSection(rsinfo),
-							  .createStmt = out->data,
+							  .createFn = printRelationStats,
+							  .createArg = rsinfo,
 							  .deps = deps,
 							  .nDeps = ndeps));
-
-	destroyPQExpBuffer(out);
-	destroyPQExpBuffer(query);
 }
 
 /*
-- 
2.48.1



  [text/x-patch] v8-0004-Batching-getAttributeStats.patch (21.6K, ../../CADkLM=c+r05srPy9w+-+nbmLEo15dKXYQ03Q_xyK+riJerigLQ@mail.gmail.com/6-v8-0004-Batching-getAttributeStats.patch)
  download | inline diff:
From 737a29c9b8f146eb037630ab183eb8601a76409a Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v8 4/4] Batching getAttributeStats().

The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.

The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
 src/bin/pg_dump/pg_dump.c | 556 ++++++++++++++++++++++++++------------
 1 file changed, 385 insertions(+), 171 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 38ba6a90106..0c26dc7a1b4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
 	zeroAsNone = 4,
 } OidOptions;
 
+typedef enum StatsBufferState
+{
+	STATSBUF_UNINITIALIZED = 0,
+	STATSBUF_ACTIVE,
+	STATSBUF_EXHAUSTED
+}			StatsBufferState;
+
+typedef struct
+{
+	PGresult   *res;			/* results from most recent
+								 * getAttributeStats() */
+	int			idx;			/* first un-consumed row of results */
+	TocEntry   *te;				/* next TOC entry to search for statsitics
+								 * data */
+
+	StatsBufferState state;		/* current state of the buffer */
+}			AttributeStatsBuffer;
+
+
 /* global decls */
 static bool dosync = true;		/* Issue fsync() to make dump durable on disk. */
 
@@ -209,6 +228,18 @@ static int	nbinaryUpgradeClassOids = 0;
 static SequenceItem *sequences = NULL;
 static int	nsequences = 0;
 
+static AttributeStatsBuffer attrstats =
+{
+	NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
 /*
  * The default number of rows per INSERT when
  * --inserts is specified without --rows-per-insert
@@ -222,6 +253,10 @@ static int	nsequences = 0;
  */
 #define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
 
+
+
+/* TODO: fmtId(const char *rawid) */
+
 /*
  * Macro for producing quoted, schema-qualified name of a dumpable object.
  */
@@ -399,6 +434,9 @@ static void setupDumpWorker(Archive *AH);
 static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
 static bool forcePartitionRootLoad(const TableInfo *tbinfo);
 static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+								const char *argname, const char *argtype,
+								const char *argval);
 
 
 int
@@ -10477,7 +10515,286 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
 }
 
 /*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData schemas;
+	PQExpBufferData relations;
+	int			numoids = 0;
+
+	Assert(AH != NULL);
+
+	/* free last result set, if any */
+	if (attrstats.state == STATSBUF_ACTIVE)
+		PQclear(attrstats.res);
+
+	/* If we have looped around to the start of the TOC, restart */
+	if (attrstats.te == AH->toc)
+		attrstats.te = AH->toc->next;
+
+	initPQExpBuffer(&schemas);
+	initPQExpBuffer(&relations);
+
+	/*
+	 * Walk ahead looking for relstats entries that are active in this
+	 * section, adding the names to the schemas and relations lists.
+	 */
+	while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+	{
+		if (attrstats.te->reqs != 0 &&
+			strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+		{
+			RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+			Assert(rsinfo != NULL);
+
+			if (numoids > 0)
+			{
+				appendPQExpBufferStr(&schemas, ",");
+				appendPQExpBufferStr(&relations, ",");
+			}
+			appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+			numoids++;
+		}
+
+		attrstats.te = attrstats.te->next;
+	}
+
+	if (numoids > 0)
+	{
+		PQExpBufferData query;
+
+		initPQExpBuffer(&query);
+		appendPQExpBuffer(&query,
+						  "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+						  schemas.data, relations.data);
+		attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+		attrstats.idx = 0;
+	}
+	else
+	{
+		attrstats.state = STATSBUF_EXHAUSTED;
+		attrstats.res = NULL;
+		attrstats.idx = -1;
+	}
+
+	termPQExpBuffer(&schemas);
+	termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData query;
+
+	Assert(AH != NULL);
+	initPQExpBuffer(&query);
+
+	appendPQExpBufferStr(&query,
+						 "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+						 "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+						 "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+						 "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+						 "s.most_common_elems, s.most_common_elem_freqs, "
+						 "s.elem_count_histogram, ");
+
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(&query,
+							 "s.range_length_histogram, "
+							 "s.range_empty_frac, "
+							 "s.range_bounds_histogram ");
+	else
+		appendPQExpBufferStr(&query,
+							 "NULL AS range_length_histogram, "
+							 "NULL AS range_empty_frac, "
+							 " NULL AS range_bounds_histogram ");
+
+	/*
+	 * The results must be in the order of relations supplied in the
+	 * parameters to ensure that they are in sync with a walk of the TOC.
+	 *
+	 * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+	 * is a way to lead the query into using the index
+	 * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+	 * expensive full scan of pg_stats.
+	 *
+	 * We may need to adjust this query for versions that are not so easily
+	 * led.
+	 */
+	appendPQExpBufferStr(&query,
+						 "FROM pg_catalog.pg_stats AS s "
+						 "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+						 "ON s.schemaname = u.schemaname "
+						 "AND s.tablename = u.tablename "
+						 "WHERE s.tablename = ANY($2) "
+						 "ORDER BY u.ord, s.attname, s.inherited");
+
+	ExecuteSqlStatement(fout, query.data);
+
+	termPQExpBuffer(&query);
+
+	attrstats.te = AH->toc->next;
+
+	fetchNextAttributeStats(fout);
+
+	attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+					 const RelStatsInfo *rsinfo)
+{
+	PGresult   *res = attrstats.res;
+	int			tup_num = attrstats.idx;
+
+	const char *attname;
+
+	static bool indexes_set = false;
+	static int	i_attname,
+				i_inherited,
+				i_null_frac,
+				i_avg_width,
+				i_n_distinct,
+				i_most_common_vals,
+				i_most_common_freqs,
+				i_histogram_bounds,
+				i_correlation,
+				i_most_common_elems,
+				i_most_common_elem_freqs,
+				i_elem_count_histogram,
+				i_range_length_histogram,
+				i_range_empty_frac,
+				i_range_bounds_histogram;
+
+	if (!indexes_set)
+	{
+		/*
+		 * It's a prepared statement, so the indexes will be the same for all
+		 * result sets, so we only need to set them once.
+		 */
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		indexes_set = true;
+	}
+
+	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+					  fout->remoteVersion);
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+	if (PQgetisnull(res, tup_num, i_attname))
+		pg_fatal("attname cannot be NULL");
+	attname = PQgetvalue(res, tup_num, i_attname);
+
+	/*
+	 * Indexes look up attname in indAttNames to derive attnum, all others use
+	 * attname directly.  We must specify attnum for indexes, since their
+	 * attnames are not necessarily stable across dump/reload.
+	 */
+	if (rsinfo->nindAttNames == 0)
+	{
+		appendPQExpBuffer(out, ",\n\t'attname', ");
+		appendStringLiteralAH(out, attname, fout);
+	}
+	else
+	{
+		bool		found = false;
+
+		for (int i = 0; i < rsinfo->nindAttNames; i++)
+			if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+			{
+				appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+								  i + 1);
+				found = true;
+				break;
+			}
+
+		if (!found)
+			pg_fatal("could not find index attname \"%s\"", attname);
+	}
+
+	if (!PQgetisnull(res, tup_num, i_inherited))
+		appendNamedArgument(out, fout, "inherited", "boolean",
+							PQgetvalue(res, tup_num, i_inherited));
+	if (!PQgetisnull(res, tup_num, i_null_frac))
+		appendNamedArgument(out, fout, "null_frac", "real",
+							PQgetvalue(res, tup_num, i_null_frac));
+	if (!PQgetisnull(res, tup_num, i_avg_width))
+		appendNamedArgument(out, fout, "avg_width", "integer",
+							PQgetvalue(res, tup_num, i_avg_width));
+	if (!PQgetisnull(res, tup_num, i_n_distinct))
+		appendNamedArgument(out, fout, "n_distinct", "real",
+							PQgetvalue(res, tup_num, i_n_distinct));
+	if (!PQgetisnull(res, tup_num, i_most_common_vals))
+		appendNamedArgument(out, fout, "most_common_vals", "text",
+							PQgetvalue(res, tup_num, i_most_common_vals));
+	if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+		appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_freqs));
+	if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+		appendNamedArgument(out, fout, "histogram_bounds", "text",
+							PQgetvalue(res, tup_num, i_histogram_bounds));
+	if (!PQgetisnull(res, tup_num, i_correlation))
+		appendNamedArgument(out, fout, "correlation", "real",
+							PQgetvalue(res, tup_num, i_correlation));
+	if (!PQgetisnull(res, tup_num, i_most_common_elems))
+		appendNamedArgument(out, fout, "most_common_elems", "text",
+							PQgetvalue(res, tup_num, i_most_common_elems));
+	if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+		appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+	if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+		appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+							PQgetvalue(res, tup_num, i_elem_count_histogram));
+	if (fout->remoteVersion >= 170000)
+	{
+		if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+			appendNamedArgument(out, fout, "range_length_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_length_histogram));
+		if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+			appendNamedArgument(out, fout, "range_empty_frac", "real",
+								PQgetvalue(res, tup_num, i_range_empty_frac));
+		if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+			appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_bounds_histogram));
+	}
+	appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
  *
  * Generate the SQL statements needed to restore a relation's statistics.
  */
@@ -10485,64 +10802,21 @@ static char *
 printRelationStats(Archive *fout, const void *userArg)
 {
 	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
-	const DumpableObject *dobj = &rsinfo->dobj;
+	const DumpableObject *dobj;
+	const char *relschema;
+	const char *relname;
+
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
 
-	PQExpBufferData query;
 	PQExpBufferData out;
 
-	PGresult   *res;
-
-	static bool first_query = true;
-	static int	i_attname;
-	static int	i_inherited;
-	static int	i_null_frac;
-	static int	i_avg_width;
-	static int	i_n_distinct;
-	static int	i_most_common_vals;
-	static int	i_most_common_freqs;
-	static int	i_histogram_bounds;
-	static int	i_correlation;
-	static int	i_most_common_elems;
-	static int	i_most_common_elem_freqs;
-	static int	i_elem_count_histogram;
-	static int	i_range_length_histogram;
-	static int	i_range_empty_frac;
-	static int	i_range_bounds_histogram;
-
-	initPQExpBuffer(&query);
-
-	if (first_query)
-	{
-		appendPQExpBufferStr(&query,
-							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
-							 "SELECT s.attname, s.inherited, "
-							 "s.null_frac, s.avg_width, s.n_distinct, "
-							 "s.most_common_vals, s.most_common_freqs, "
-							 "s.histogram_bounds, s.correlation, "
-							 "s.most_common_elems, s.most_common_elem_freqs, "
-							 "s.elem_count_histogram, ");
-
-		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(&query,
-								 "s.range_length_histogram, "
-								 "s.range_empty_frac, "
-								 "s.range_bounds_histogram ");
-		else
-			appendPQExpBufferStr(&query,
-								 "NULL AS range_length_histogram,"
-								 "NULL AS range_empty_frac,"
-								 "NULL AS range_bounds_histogram ");
-
-		appendPQExpBufferStr(&query,
-							 "FROM pg_catalog.pg_stats s "
-							 "WHERE s.schemaname = $1 "
-							 "AND s.tablename = $2 "
-							 "ORDER BY s.attname, s.inherited");
-
-		ExecuteSqlStatement(fout, query.data);
-
-		resetPQExpBuffer(&query);
-	}
+	Assert(rsinfo != NULL);
+	dobj = &rsinfo->dobj;
+	Assert(dobj != NULL);
+	relschema = dobj->namespace->dobj.name;
+	Assert(relschema != NULL);
+	relname = dobj->name;
+	Assert(relname != NULL);
 
 	initPQExpBuffer(&out);
 
@@ -10561,132 +10835,72 @@ printRelationStats(Archive *fout, const void *userArg)
 	appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
 					  rsinfo->relallvisible);
 
-	/* fetch attribute stats */
-	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(&query, ", ");
-	appendStringLiteralAH(&query, dobj->name, fout);
-	appendPQExpBufferStr(&query, ")");
+	AH->txnCount++;
 
-	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+	if (attrstats.state == STATSBUF_UNINITIALIZED)
+		initAttributeStats(fout);
 
-	if (first_query)
+	/*
+	 * Because the query returns rows in the same order as the relations
+	 * requested, and because every relation gets at least one row in the
+	 * result set, the first row for this relation must correspond either to
+	 * the current row of this result set (if one exists) or the first row of
+	 * the next result set (if this one is already consumed).
+	 */
+	if (attrstats.state != STATSBUF_ACTIVE)
+		pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+				 rsinfo->dobj.namespace->dobj.name,
+				 rsinfo->dobj.name);
+
+	/*
+	 * If the current result set has been fully consumed, then the row(s) we
+	 * need (if any) would be found in the next one. This will update
+	 * attrstats.res and attrstats.idx.
+	 */
+	if (PQntuples(attrstats.res) <= attrstats.idx)
+		fetchNextAttributeStats(fout);
+
+	while (true)
 	{
-		i_attname = PQfnumber(res, "attname");
-		i_inherited = PQfnumber(res, "inherited");
-		i_null_frac = PQfnumber(res, "null_frac");
-		i_avg_width = PQfnumber(res, "avg_width");
-		i_n_distinct = PQfnumber(res, "n_distinct");
-		i_most_common_vals = PQfnumber(res, "most_common_vals");
-		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-		i_correlation = PQfnumber(res, "correlation");
-		i_most_common_elems = PQfnumber(res, "most_common_elems");
-		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
-		first_query = false;
-	}
-
-	/* restore attribute stats */
-	for (int rownum = 0; rownum < PQntuples(res); rownum++)
-	{
-		const char *attname;
-
-		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
-						  fout->remoteVersion);
-		appendPQExpBufferStr(&out, "\t'schemaname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(&out, ",\n\t'relname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
-		if (PQgetisnull(res, rownum, i_attname))
-			pg_fatal("attname cannot be NULL");
-		attname = PQgetvalue(res, rownum, i_attname);
+		int			i_schemaname;
+		int			i_tablename;
+		char	   *schemaname;
+		char	   *tablename;	/* misnomer, following pg_stats naming */
 
 		/*
-		 * Indexes look up attname in indAttNames to derive attnum, all others
-		 * use attname directly.  We must specify attnum for indexes, since
-		 * their attnames are not necessarily stable across dump/reload.
+		 * If we hit the end of the result set, then there are no more records
+		 * for this relation, so we should stop, but first get the next result
+		 * set for the next batch of relations.
 		 */
-		if (rsinfo->nindAttNames == 0)
+		if (PQntuples(attrstats.res) <= attrstats.idx)
 		{
-			appendPQExpBuffer(&out, ",\n\t'attname', ");
-			appendStringLiteralAH(&out, attname, fout);
-		}
-		else
-		{
-			bool		found = false;
-
-			for (int i = 0; i < rsinfo->nindAttNames; i++)
-			{
-				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
-				{
-					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
-									  i + 1);
-					found = true;
-					break;
-				}
-			}
-
-			if (!found)
-				pg_fatal("could not find index attname \"%s\"", attname);
+			fetchNextAttributeStats(fout);
+			break;
 		}
 
-		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(&out, fout, "inherited", "boolean",
-								PQgetvalue(res, rownum, i_inherited));
-		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(&out, fout, "null_frac", "real",
-								PQgetvalue(res, rownum, i_null_frac));
-		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(&out, fout, "avg_width", "integer",
-								PQgetvalue(res, rownum, i_avg_width));
-		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(&out, fout, "n_distinct", "real",
-								PQgetvalue(res, rownum, i_n_distinct));
-		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(&out, fout, "most_common_vals", "text",
-								PQgetvalue(res, rownum, i_most_common_vals));
-		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_freqs));
-		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(&out, fout, "histogram_bounds", "text",
-								PQgetvalue(res, rownum, i_histogram_bounds));
-		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(&out, fout, "correlation", "real",
-								PQgetvalue(res, rownum, i_correlation));
-		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(&out, fout, "most_common_elems", "text",
-								PQgetvalue(res, rownum, i_most_common_elems));
-		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_elem_freqs));
-		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
-								PQgetvalue(res, rownum, i_elem_count_histogram));
-		if (fout->remoteVersion >= 170000)
-		{
-			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(&out, fout, "range_length_histogram", "text",
-									PQgetvalue(res, rownum, i_range_length_histogram));
-			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(&out, fout, "range_empty_frac", "real",
-									PQgetvalue(res, rownum, i_range_empty_frac));
-			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
-									PQgetvalue(res, rownum, i_range_bounds_histogram));
-		}
-		appendPQExpBufferStr(&out, "\n);\n");
+		i_schemaname = PQfnumber(attrstats.res, "schemaname");
+		Assert(i_schemaname >= 0);
+		i_tablename = PQfnumber(attrstats.res, "tablename");
+		Assert(i_tablename >= 0);
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+			pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+			pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+		schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+		tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+		/* stop if current stat row isn't for this relation */
+		if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+			break;
+
+		appendAttributeStats(fout, &out, rsinfo);
+		AH->txnCount++;
+		attrstats.idx++;
 	}
 
-	PQclear(res);
-
-	termPQExpBuffer(&query);
 	return out.data;
 }
 
-- 
2.48.1



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-16 01:37                 ` Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-16 01:37 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, Mar 14, 2025 at 4:03 PM Corey Huinker <[email protected]>
wrote:

> New patches and a rebase.
>
> 0001 - no changes, but the longer I go the more I'm certain this is
> something we want to do.
> 0002- same as 0001
>
> 0003 -
>
> Storing the restore function calls in the archive entry hogged a lot of
> memory and made people nervous. This introduces a new function pointer that
> generates those restore SQL calls right before they're written to disk,
> thus reducing the memory load from "stats for every object to be dumped" to
> just one object. Thanks to Nathan for diagnosing some weird quirks with
> various formats.
>
> 0004 -
>
> This replaces the query in the prepared statement with one that batches
> them 100 relations at a time, and then maintains that result set until it
> is consumed. It seems to have obvious speedups.
>

Another rebase, and a new patch 0005 to have pg_dump fetch and restore
relallfrozen for dbs of version 18 and higher. With older versions we omit
relallfrozen and let the import function assign the default.


Attachments:

  [text/x-patch] v9-0005-Add-relallfrozen-to-pg_dump-statistics.patch (7.9K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/3-v9-0005-Add-relallfrozen-to-pg_dump-statistics.patch)
  download | inline diff:
From 79b459706b09458b4c27d3f80a8fab9ec9600ce7 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 15 Mar 2025 17:34:30 -0400
Subject: [PATCH v9 5/5] Add relallfrozen to pg_dump statistics.

The column relallfrozen was recently added to pg_class and it also
represent statistics, so we should add it to the dump/restore/upgrade
operations.

Dumps of databases prior to v18 will not attempt to restore any value to
relallfrozen, allowing pg_restore_relation_stats() to set the default it
deems appropriate.
---
 src/bin/pg_dump/pg_dump.c        | 52 ++++++++++++++++++++++----------
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl |  3 +-
 3 files changed, 39 insertions(+), 17 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 0c26dc7a1b4..249bcfb80a1 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6856,7 +6856,8 @@ getFuncs(Archive *fout)
  */
 static RelStatsInfo *
 getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
-					  char *reltuples, int32 relallvisible, char relkind,
+					  char *reltuples, int32 relallvisible,
+					  int32 relallfrozen, char relkind,
 					  char **indAttNames, int nindAttNames)
 {
 	if (!fout->dopt->dumpStatistics)
@@ -6885,6 +6886,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
 		info->relpages = relpages;
 		info->reltuples = pstrdup(reltuples);
 		info->relallvisible = relallvisible;
+		info->relallfrozen = relallfrozen;
 		info->relkind = relkind;
 		info->indAttNames = indAttNames;
 		info->nindAttNames = nindAttNames;
@@ -6924,6 +6926,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relpages;
 	int			i_reltuples;
 	int			i_relallvisible;
+	int			i_relallfrozen;
 	int			i_toastpages;
 	int			i_owning_tab;
 	int			i_owning_col;
@@ -6974,8 +6977,13 @@ getTables(Archive *fout, int *numTables)
 						 "c.relowner, "
 						 "c.relchecks, "
 						 "c.relhasindex, c.relhasrules, c.relpages, "
-						 "c.reltuples, c.relallvisible, c.relhastriggers, "
-						 "c.relpersistence, "
+						 "c.reltuples, c.relallvisible, ");
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBufferStr(query, "c.relallfrozen, ");
+
+	appendPQExpBufferStr(query,
+						 "c.relhastriggers, c.relpersistence, "
 						 "c.reloftype, "
 						 "c.relacl, "
 						 "acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
@@ -7140,6 +7148,7 @@ getTables(Archive *fout, int *numTables)
 	i_relpages = PQfnumber(res, "relpages");
 	i_reltuples = PQfnumber(res, "reltuples");
 	i_relallvisible = PQfnumber(res, "relallvisible");
+	i_relallfrozen = PQfnumber(res, "relallfrozen");
 	i_toastpages = PQfnumber(res, "toastpages");
 	i_owning_tab = PQfnumber(res, "owning_tab");
 	i_owning_col = PQfnumber(res, "owning_col");
@@ -7187,6 +7196,7 @@ getTables(Archive *fout, int *numTables)
 	for (i = 0; i < ntups; i++)
 	{
 		int32		relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+		int32		relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen));
 
 		tblinfo[i].dobj.objType = DO_TABLE;
 		tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
@@ -7289,7 +7299,7 @@ getTables(Archive *fout, int *numTables)
 		if (tblinfo[i].interesting)
 			getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages,
 								  PQgetvalue(res, i, i_reltuples),
-								  relallvisible, tblinfo[i].relkind, NULL, 0);
+								  relallvisible, relallfrozen, tblinfo[i].relkind, NULL, 0);
 
 		/*
 		 * Read-lock target tables to make sure they aren't DROPPED or altered
@@ -7558,6 +7568,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_relpages,
 				i_reltuples,
 				i_relallvisible,
+				i_relallfrozen,
 				i_parentidx,
 				i_indexdef,
 				i_indnkeyatts,
@@ -7612,7 +7623,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	appendPQExpBufferStr(query,
 						 "SELECT t.tableoid, t.oid, i.indrelid, "
 						 "t.relname AS indexname, "
-						 "t.relpages, t.reltuples, t.relallvisible, "
+						 "t.relpages, t.reltuples, t.relallvisible, ");
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBufferStr(query, "t.relallfrozen, ");
+
+	appendPQExpBufferStr(query,
 						 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
 						 "i.indkey, i.indisclustered, "
 						 "c.contype, c.conname, "
@@ -7728,6 +7744,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	i_relpages = PQfnumber(res, "relpages");
 	i_reltuples = PQfnumber(res, "reltuples");
 	i_relallvisible = PQfnumber(res, "relallvisible");
+	i_relallfrozen = PQfnumber(res, "relallfrozen");
 	i_parentidx = PQfnumber(res, "parentidx");
 	i_indexdef = PQfnumber(res, "indexdef");
 	i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7799,6 +7816,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			RelStatsInfo *relstats;
 			int32		relpages = atoi(PQgetvalue(res, j, i_relpages));
 			int32		relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
+			int32		relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen));
 
 			indxinfo[j].dobj.objType = DO_INDEX;
 			indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7841,7 +7859,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 
 			relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
 											 PQgetvalue(res, j, i_reltuples),
-											 relallvisible, indexkind,
+											 relallvisible, relallfrozen, indexkind,
 											 indAttNames, nindAttNames);
 
 			contype = *(PQgetvalue(res, j, i_contype));
@@ -10821,19 +10839,21 @@ printRelationStats(Archive *fout, const void *userArg)
 	initPQExpBuffer(&out);
 
 	/* restore relation stats */
-	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-	appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
+	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(");
+	appendPQExpBuffer(&out, "\n\t'version', '%u'::integer",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(&out, "\t'schemaname', ");
+	appendPQExpBufferStr(&out, ",\n\t'schemaname', ");
 	appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
-	appendPQExpBufferStr(&out, ",\n");
-	appendPQExpBufferStr(&out, "\t'relname', ");
+	appendPQExpBufferStr(&out, ",\n\t'relname', ");
 	appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-	appendPQExpBufferStr(&out, ",\n");
-	appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
-					  rsinfo->relallvisible);
+	appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages);
+	appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples);
+	appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible);
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+
+	appendPQExpBufferStr(&out, "\n);\n");
 
 	AH->txnCount++;
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index bbdb30b5f54..82f1eb3c4b7 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -441,6 +441,7 @@ typedef struct _relStatsInfo
 	int32		relpages;
 	char	   *reltuples;
 	int32		relallvisible;
+	int32		relallfrozen;
 	char		relkind;		/* 'r', 'm', 'i', etc */
 
 	/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index b037f239136..1d69e55a861 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4729,7 +4729,8 @@ my %tests = (
 			'relname',\s'dup_test_post_data_ix',\s+
 			'relpages',\s'\d+'::integer,\s+
 			'reltuples',\s'\d+'::real,\s+
-			'relallvisible',\s'\d+'::integer\s+
+			'relallvisible',\s'\d+'::integer,\s+
+			'relallfrozen',\s'\d+'::integer\s+
 			\);\s+
 			\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-- 
2.48.1



  [text/x-patch] v9-0003-Introduce-CreateStmtPtr.patch (17.2K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/4-v9-0003-Introduce-CreateStmtPtr.patch)
  download | inline diff:
From 72d0fe4b5de382a2f36151de2b26960d3a85267f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v9 3/5] Introduce CreateStmtPtr.

CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.

Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
 src/bin/pg_dump/pg_backup.h          |   2 +
 src/bin/pg_dump/pg_backup_archiver.c |  22 ++-
 src/bin/pg_dump/pg_backup_archiver.h |   7 +
 src/bin/pg_dump/pg_dump.c            | 230 +++++++++++++++------------
 4 files changed, 156 insertions(+), 105 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index e783cc68d89..bb175874a5a 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -287,6 +287,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
 
 typedef void (*SetupWorkerPtrType) (Archive *AH);
 
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
 /*
  * Main archiver interface.
  */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 7480e122b61..3fcfecf6719 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1263,6 +1263,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
 	newToc->dataDumper = opts->dumpFn;
 	newToc->dataDumperArg = opts->dumpArg;
 	newToc->hadDumper = opts->dumpFn ? true : false;
+	newToc->createDumper = opts->createFn;
+	newToc->createDumperArg = opts->createArg;
+	newToc->hadCreateDumper = opts->createFn ? true : false;
 
 	newToc->formatData = NULL;
 	newToc->dataLength = 0;
@@ -2619,7 +2622,17 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->tag);
 		WriteStr(AH, te->desc);
 		WriteInt(AH, te->section);
-		WriteStr(AH, te->defn);
+
+		if (te->hadCreateDumper)
+		{
+			char	   *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+			WriteStr(AH, defn);
+			pg_free(defn);
+		}
+		else
+			WriteStr(AH, te->defn);
+
 		WriteStr(AH, te->dropStmt);
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
@@ -3849,6 +3862,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
 	{
 		IssueACLPerBlob(AH, te);
 	}
+	else if (te->hadCreateDumper)
+	{
+		char	   *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+		ahwrite(ptr, 1, strlen(ptr), AH);
+		pg_free(ptr);
+	}
 	else if (te->defn && strlen(te->defn) > 0)
 	{
 		ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
 	const void *dataDumperArg;	/* Arg for above routine */
 	void	   *formatData;		/* TOC Entry data specific to file format */
 
+	CreateStmtPtr createDumper; /* Routine for create statement creation */
+	const void *createDumperArg;	/* arg for the above routine */
+	bool		hadCreateDumper;	/* Archiver was passed a create statement
+									 * routine */
+
 	/* working state while dumping/restoring */
 	pgoff_t		dataLength;		/* item's data size; 0 if none or unknown */
 	int			reqs;			/* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
 	int			nDeps;
 	DataDumperPtr dumpFn;
 	const void *dumpArg;
+	CreateStmtPtr createFn;
+	const void *createArg;
 } ArchiveOpts;
 #define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
 /* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bd857bb076c..38ba6a90106 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10477,51 +10477,44 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
 }
 
 /*
- * dumpRelationStats --
+ * printDumpRelationStats --
  *
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
  */
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
 {
+	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
 	const DumpableObject *dobj = &rsinfo->dobj;
+
+	PQExpBufferData query;
+	PQExpBufferData out;
+
 	PGresult   *res;
-	PQExpBuffer query;
-	PQExpBuffer out;
-	DumpId	   *deps = NULL;
-	int			ndeps = 0;
-	int			i_attname;
-	int			i_inherited;
-	int			i_null_frac;
-	int			i_avg_width;
-	int			i_n_distinct;
-	int			i_most_common_vals;
-	int			i_most_common_freqs;
-	int			i_histogram_bounds;
-	int			i_correlation;
-	int			i_most_common_elems;
-	int			i_most_common_elem_freqs;
-	int			i_elem_count_histogram;
-	int			i_range_length_histogram;
-	int			i_range_empty_frac;
-	int			i_range_bounds_histogram;
 
-	/* nothing to do if we are not dumping statistics */
-	if (!fout->dopt->dumpStatistics)
-		return;
+	static bool first_query = true;
+	static int	i_attname;
+	static int	i_inherited;
+	static int	i_null_frac;
+	static int	i_avg_width;
+	static int	i_n_distinct;
+	static int	i_most_common_vals;
+	static int	i_most_common_freqs;
+	static int	i_histogram_bounds;
+	static int	i_correlation;
+	static int	i_most_common_elems;
+	static int	i_most_common_elem_freqs;
+	static int	i_elem_count_histogram;
+	static int	i_range_length_histogram;
+	static int	i_range_empty_frac;
+	static int	i_range_bounds_histogram;
 
-	/* dependent on the relation definition, if doing schema */
-	if (fout->dopt->dumpSchema)
+	initPQExpBuffer(&query);
+
+	if (first_query)
 	{
-		deps = dobj->dependencies;
-		ndeps = dobj->nDeps;
-	}
-
-	query = createPQExpBuffer();
-	if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
-	{
-		appendPQExpBufferStr(query,
-							 "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+		appendPQExpBufferStr(&query,
+							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
 							 "SELECT s.attname, s.inherited, "
 							 "s.null_frac, s.avg_width, s.n_distinct, "
 							 "s.most_common_vals, s.most_common_freqs, "
@@ -10530,82 +10523,85 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							 "s.elem_count_histogram, ");
 
 		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "s.range_length_histogram, "
 								 "s.range_empty_frac, "
 								 "s.range_bounds_histogram ");
 		else
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "NULL AS range_length_histogram,"
 								 "NULL AS range_empty_frac,"
 								 "NULL AS range_bounds_histogram ");
 
-		appendPQExpBufferStr(query,
+		appendPQExpBufferStr(&query,
 							 "FROM pg_catalog.pg_stats s "
 							 "WHERE s.schemaname = $1 "
 							 "AND s.tablename = $2 "
 							 "ORDER BY s.attname, s.inherited");
 
-		ExecuteSqlStatement(fout, query->data);
+		ExecuteSqlStatement(fout, query.data);
 
-		fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
-		resetPQExpBuffer(query);
+		resetPQExpBuffer(&query);
 	}
 
-	out = createPQExpBuffer();
+	initPQExpBuffer(&out);
 
 	/* restore relation stats */
-	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
+	appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'schemaname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBufferStr(out, "\t'relname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+	appendPQExpBufferStr(&out, "\t'schemaname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(&out, ",\n");
+	appendPQExpBufferStr(&out, "\t'relname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+	appendPQExpBufferStr(&out, ",\n");
+	appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+	appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+	appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
 					  rsinfo->relallvisible);
 
 	/* fetch attribute stats */
-	appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(query, ", ");
-	appendStringLiteralAH(query, dobj->name, fout);
-	appendPQExpBufferStr(query, ");");
+	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+	appendPQExpBufferStr(&query, ", ");
+	appendStringLiteralAH(&query, dobj->name, fout);
+	appendPQExpBufferStr(&query, ")");
 
-	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
 
-	i_attname = PQfnumber(res, "attname");
-	i_inherited = PQfnumber(res, "inherited");
-	i_null_frac = PQfnumber(res, "null_frac");
-	i_avg_width = PQfnumber(res, "avg_width");
-	i_n_distinct = PQfnumber(res, "n_distinct");
-	i_most_common_vals = PQfnumber(res, "most_common_vals");
-	i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-	i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-	i_correlation = PQfnumber(res, "correlation");
-	i_most_common_elems = PQfnumber(res, "most_common_elems");
-	i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-	i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-	i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-	i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-	i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+	if (first_query)
+	{
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		first_query = false;
+	}
 
 	/* restore attribute stats */
 	for (int rownum = 0; rownum < PQntuples(res); rownum++)
 	{
 		const char *attname;
 
-		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'schemaname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(out, ",\n\t'relname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+		appendPQExpBufferStr(&out, "\t'schemaname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(&out, ",\n\t'relname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10618,8 +10614,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 */
 		if (rsinfo->nindAttNames == 0)
 		{
-			appendPQExpBuffer(out, ",\n\t'attname', ");
-			appendStringLiteralAH(out, attname, fout);
+			appendPQExpBuffer(&out, ",\n\t'attname', ");
+			appendStringLiteralAH(&out, attname, fout);
 		}
 		else
 		{
@@ -10629,7 +10625,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 			{
 				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
 				{
-					appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
 									  i + 1);
 					found = true;
 					break;
@@ -10641,67 +10637,93 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		}
 
 		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(out, fout, "inherited", "boolean",
+			appendNamedArgument(&out, fout, "inherited", "boolean",
 								PQgetvalue(res, rownum, i_inherited));
 		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(out, fout, "null_frac", "real",
+			appendNamedArgument(&out, fout, "null_frac", "real",
 								PQgetvalue(res, rownum, i_null_frac));
 		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(out, fout, "avg_width", "integer",
+			appendNamedArgument(&out, fout, "avg_width", "integer",
 								PQgetvalue(res, rownum, i_avg_width));
 		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(out, fout, "n_distinct", "real",
+			appendNamedArgument(&out, fout, "n_distinct", "real",
 								PQgetvalue(res, rownum, i_n_distinct));
 		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(out, fout, "most_common_vals", "text",
+			appendNamedArgument(&out, fout, "most_common_vals", "text",
 								PQgetvalue(res, rownum, i_most_common_vals));
 		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_freqs));
 		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(out, fout, "histogram_bounds", "text",
+			appendNamedArgument(&out, fout, "histogram_bounds", "text",
 								PQgetvalue(res, rownum, i_histogram_bounds));
 		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(out, fout, "correlation", "real",
+			appendNamedArgument(&out, fout, "correlation", "real",
 								PQgetvalue(res, rownum, i_correlation));
 		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(out, fout, "most_common_elems", "text",
+			appendNamedArgument(&out, fout, "most_common_elems", "text",
 								PQgetvalue(res, rownum, i_most_common_elems));
 		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_elem_freqs));
 		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
 								PQgetvalue(res, rownum, i_elem_count_histogram));
 		if (fout->remoteVersion >= 170000)
 		{
 			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(out, fout, "range_length_histogram", "text",
+				appendNamedArgument(&out, fout, "range_length_histogram", "text",
 									PQgetvalue(res, rownum, i_range_length_histogram));
 			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(out, fout, "range_empty_frac", "real",
+				appendNamedArgument(&out, fout, "range_empty_frac", "real",
 									PQgetvalue(res, rownum, i_range_empty_frac));
 			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
 									PQgetvalue(res, rownum, i_range_bounds_histogram));
 		}
-		appendPQExpBufferStr(out, "\n);\n");
+		appendPQExpBufferStr(&out, "\n);\n");
 	}
 
 	PQclear(res);
 
+	termPQExpBuffer(&query);
+	return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+	const DumpableObject *dobj = &rsinfo->dobj;
+
+	DumpId	   *deps = NULL;
+	int			ndeps = 0;
+
+	/* nothing to do if we are not dumping statistics */
+	if (!fout->dopt->dumpStatistics)
+		return;
+
+	/* dependent on the relation definition, if doing schema */
+	if (fout->dopt->dumpSchema)
+	{
+		deps = dobj->dependencies;
+		ndeps = dobj->nDeps;
+	}
+
 	ArchiveEntry(fout, nilCatalogId, createDumpId(),
 				 ARCHIVE_OPTS(.tag = dobj->name,
 							  .namespace = dobj->namespace->dobj.name,
 							  .description = "STATISTICS DATA",
 							  .section = rsinfo->postponed_def ?
 							  SECTION_POST_DATA : statisticsDumpSection(rsinfo),
-							  .createStmt = out->data,
+							  .createFn = printRelationStats,
+							  .createArg = rsinfo,
 							  .deps = deps,
 							  .nDeps = ndeps));
-
-	destroyPQExpBuffer(out);
-	destroyPQExpBuffer(query);
 }
 
 /*
-- 
2.48.1



  [text/x-patch] v9-0001-Split-relation-into-schemaname-and-relname.patch (65.0K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/5-v9-0001-Split-relation-into-schemaname-and-relname.patch)
  download | inline diff:
From fd2cced89c11353e909e6ef9bb824a3a7536e6cb Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v9 1/5] Split relation into schemaname and relname.

In order to further reduce potential error-failures in restores and
upgrades, replace the numerous casts of fully qualified relation names
into their schema+relname text components.

Further remove the ::name casts on attname and change the expected
datatype to text.

Add an ACL_USAGE check on the namespace oid after it is looked up.
---
 src/include/catalog/pg_proc.dat            |   8 +-
 src/include/statistics/stat_utils.h        |   2 +
 src/backend/statistics/attribute_stats.c   |  87 ++++--
 src/backend/statistics/relation_stats.c    |  65 +++--
 src/backend/statistics/stat_utils.c        |  37 +++
 src/bin/pg_dump/pg_dump.c                  |  25 +-
 src/bin/pg_dump/t/002_pg_dump.pl           |   6 +-
 src/test/regress/expected/stats_import.out | 307 +++++++++++++--------
 src/test/regress/sql/stats_import.sql      | 276 +++++++++++-------
 doc/src/sgml/func.sgml                     |  41 +--
 10 files changed, 566 insertions(+), 288 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 890822eaf79..8dee321d248 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12453,8 +12453,8 @@
   descr => 'clear statistics on relation',
   proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
   proparallel => 'u', prorettype => 'void',
-  proargtypes => 'regclass',
-  proargnames => '{relation}',
+  proargtypes => 'text text',
+  proargnames => '{schemaname,relname}',
   prosrc => 'pg_clear_relation_stats' },
 { oid => '8461',
   descr => 'restore statistics on attribute',
@@ -12469,8 +12469,8 @@
   descr => 'clear statistics on attribute',
   proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
   proparallel => 'u', prorettype => 'void',
-  proargtypes => 'regclass name bool',
-  proargnames => '{relation,attname,inherited}',
+  proargtypes => 'text text text bool',
+  proargnames => '{schemaname,relname,attname,inherited}',
   prosrc => 'pg_clear_attribute_stats' },
 
 # GiST stratnum implementations
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 0eb4decfcac..cad042c8e4a 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -32,6 +32,8 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 
 extern void stats_lock_check_privileges(Oid reloid);
 
+extern Oid stats_schema_check_privileges(const char *nspname);
+
 extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 											 FunctionCallInfo positional_fcinfo,
 											 struct StatsArgInfo *arginfo);
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..f87db2d6102 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -36,7 +36,8 @@
 
 enum attribute_stats_argnum
 {
-	ATTRELATION_ARG = 0,
+	ATTRELSCHEMA_ARG = 0,
+	ATTRELNAME_ARG,
 	ATTNAME_ARG,
 	ATTNUM_ARG,
 	INHERITED_ARG,
@@ -58,8 +59,9 @@ enum attribute_stats_argnum
 
 static struct StatsArgInfo attarginfo[] =
 {
-	[ATTRELATION_ARG] = {"relation", REGCLASSOID},
-	[ATTNAME_ARG] = {"attname", NAMEOID},
+	[ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+	[ATTRELNAME_ARG] = {"relname", TEXTOID},
+	[ATTNAME_ARG] = {"attname", TEXTOID},
 	[ATTNUM_ARG] = {"attnum", INT2OID},
 	[INHERITED_ARG] = {"inherited", BOOLOID},
 	[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +82,8 @@ static struct StatsArgInfo attarginfo[] =
 
 enum clear_attribute_stats_argnum
 {
-	C_ATTRELATION_ARG = 0,
+	C_ATTRELSCHEMA_ARG = 0,
+	C_ATTRELNAME_ARG,
 	C_ATTNAME_ARG,
 	C_INHERITED_ARG,
 	C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +91,9 @@ enum clear_attribute_stats_argnum
 
 static struct StatsArgInfo cleararginfo[] =
 {
-	[C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
-	[C_ATTNAME_ARG] = {"attname", NAMEOID},
+	[C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+	[C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+	[C_ATTNAME_ARG] = {"attname", TEXTOID},
 	[C_INHERITED_ARG] = {"inherited", BOOLOID},
 	[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
 };
@@ -133,6 +137,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
 {
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
 	char	   *attname;
 	AttrNumber	attnum;
@@ -170,8 +177,23 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
-	reloid = PG_GETARG_OID(ATTRELATION_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (nspoid == InvalidOid)
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (reloid == InvalidOid)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -185,21 +207,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
-		Name		attnamename;
-
 		if (!PG_ARGISNULL(ATTNUM_ARG))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
-		attnamename = PG_GETARG_NAME(ATTNAME_ARG);
-		attname = NameStr(*attnamename);
+		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column \"%s\" of relation \"%s\" does not exist",
-							attname, get_rel_name(reloid))));
+					 errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+							attname, nspname, relname)));
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -210,8 +229,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 			!SearchSysCacheExistsAttName(reloid, attname))
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column %d of relation \"%s\" does not exist",
-							attnum, get_rel_name(reloid))));
+					 errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+							attnum, nspname, relname)));
 	}
 	else
 	{
@@ -900,13 +919,33 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
 Datum
 pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 {
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
-	Name		attname;
+	char	   *attname;
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
-	reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (!OidIsValid(nspoid))
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (!OidIsValid(reloid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -916,23 +955,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 
 	stats_lock_check_privileges(reloid);
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	attname = PG_GETARG_NAME(C_ATTNAME_ARG);
-	attnum = get_attnum(reloid, NameStr(*attname));
+	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
-						NameStr(*attname))));
+						attname)));
 
 	if (attnum == InvalidAttrNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
-						NameStr(*attname), get_rel_name(reloid))));
+						attname, get_rel_name(reloid))));
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
 	delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 52dfa477187..fdc69bc93e2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
 
 #include "access/heapam.h"
 #include "catalog/indexing.h"
+#include "catalog/namespace.h"
 #include "statistics/stat_utils.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 
@@ -32,7 +35,8 @@
 
 enum relation_stats_argnum
 {
-	RELATION_ARG = 0,
+	RELSCHEMA_ARG = 0,
+	RELNAME_ARG,
 	RELPAGES_ARG,
 	RELTUPLES_ARG,
 	RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
 
 static struct StatsArgInfo relarginfo[] =
 {
-	[RELATION_ARG] = {"relation", REGCLASSOID},
+	[RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+	[RELNAME_ARG] = {"relname", TEXTOID},
 	[RELPAGES_ARG] = {"relpages", INT4OID},
 	[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
 	[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
 relation_statistics_update(FunctionCallInfo fcinfo)
 {
 	bool		result = true;
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
 	Relation	crel;
 	BlockNumber relpages = 0;
@@ -76,6 +84,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
+	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (!OidIsValid(nspoid))
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (!OidIsValid(reloid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	stats_lock_check_privileges(reloid);
+
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
 		relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +142,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 		update_relallfrozen = true;
 	}
 
-	stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
-	reloid = PG_GETARG_OID(RELATION_ARG);
-
-	if (RecoveryInProgress())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("recovery is in progress"),
-				 errhint("Statistics cannot be modified during recovery.")));
-
-	stats_lock_check_privileges(reloid);
-
 	/*
 	 * Take RowExclusiveLock on pg_class, consistent with
 	 * vac_update_relstats().
@@ -187,20 +210,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 Datum
 pg_clear_relation_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(newfcinfo, 5);
+	LOCAL_FCINFO(newfcinfo, 6);
 
-	InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+	InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
 
-	newfcinfo->args[0].value = PG_GETARG_OID(0);
+	newfcinfo->args[0].value = PG_GETARG_DATUM(0);
 	newfcinfo->args[0].isnull = PG_ARGISNULL(0);
-	newfcinfo->args[1].value = UInt32GetDatum(0);
-	newfcinfo->args[1].isnull = false;
-	newfcinfo->args[2].value = Float4GetDatum(-1.0);
+	newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+	newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+	newfcinfo->args[2].value = UInt32GetDatum(0);
 	newfcinfo->args[2].isnull = false;
-	newfcinfo->args[3].value = UInt32GetDatum(0);
+	newfcinfo->args[3].value = Float4GetDatum(-1.0);
 	newfcinfo->args[3].isnull = false;
 	newfcinfo->args[4].value = UInt32GetDatum(0);
 	newfcinfo->args[4].isnull = false;
+	newfcinfo->args[5].value = UInt32GetDatum(0);
+	newfcinfo->args[5].isnull = false;
 
 	relation_statistics_update(newfcinfo);
 	PG_RETURN_VOID();
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 9647f5108b3..e037d4994e8 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -18,7 +18,9 @@
 
 #include "access/relation.h"
 #include "catalog/index.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "statistics/stat_utils.h"
@@ -213,6 +215,41 @@ stats_lock_check_privileges(Oid reloid)
 	relation_close(table, NoLock);
 }
 
+
+/*
+ * Resolve a schema name into an Oid, ensure that the user has usage privs on
+ * that schema.
+ */
+Oid
+stats_schema_check_privileges(const char *nspname)
+{
+	Oid			nspoid;
+	AclResult	aclresult;
+
+	nspoid = get_namespace_oid(nspname, true);
+
+	if (nspoid == InvalidOid)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_SCHEMA_NAME),
+				 errmsg("schema %s does not exist", nspname)));
+		return InvalidOid;
+	}
+
+	aclresult = object_aclcheck(NamespaceRelationId, nspoid, GetUserId(), ACL_USAGE);
+
+	if (aclresult != ACLCHECK_OK)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for schema %s", nspname)));
+		return InvalidOid;
+	}
+
+	return nspoid;
+}
+
+
 /*
  * Find the argument number for the given argument name, returning -1 if not
  * found.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c371570501a..bd857bb076c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10490,7 +10490,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 	PQExpBuffer out;
 	DumpId	   *deps = NULL;
 	int			ndeps = 0;
-	char	   *qualified_name;
 	int			i_attname;
 	int			i_inherited;
 	int			i_null_frac;
@@ -10555,15 +10554,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 
 	out = createPQExpBuffer();
 
-	qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
 	/* restore relation stats */
 	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
 	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'relation', ");
-	appendStringLiteralAH(out, qualified_name, fout);
-	appendPQExpBufferStr(out, "::regclass,\n");
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n");
+	appendPQExpBufferStr(out, "\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n");
 	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
 	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
 	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
@@ -10602,9 +10602,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
 		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'relation', ");
-		appendStringLiteralAH(out, qualified_name, fout);
-		appendPQExpBufferStr(out, "::regclass");
+		appendPQExpBufferStr(out, "\t'schemaname', ");
+		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(out, ",\n\t'relname', ");
+		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10616,7 +10617,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 * their attnames are not necessarily stable across dump/reload.
 		 */
 		if (rsinfo->nindAttNames == 0)
-			appendNamedArgument(out, fout, "attname", "name", attname);
+		{
+			appendPQExpBuffer(out, ",\n\t'attname', ");
+			appendStringLiteralAH(out, attname, fout);
+		}
 		else
 		{
 			bool		found = false;
@@ -10696,7 +10700,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							  .deps = deps,
 							  .nDeps = ndeps));
 
-	free(qualified_name);
 	destroyPQExpBuffer(out);
 	destroyPQExpBuffer(query);
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c7bffc1b045..b037f239136 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4725,14 +4725,16 @@ my %tests = (
 		regexp => qr/^
 			\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-			'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+			'schemaname',\s'dump_test',\s+
+			'relname',\s'dup_test_post_data_ix',\s+
 			'relpages',\s'\d+'::integer,\s+
 			'reltuples',\s'\d+'::real,\s+
 			'relallvisible',\s'\d+'::integer\s+
 			\);\s+
 			\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-			'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+			'schemaname',\s'dump_test',\s+
+			'relname',\s'dup_test_post_data_ix',\s+
 			'attnum',\s'2'::smallint,\s+
 			'inherited',\s'f'::boolean,\s+
 			'null_frac',\s'0'::real,\s+
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f46d5e7854..2f1295f2149 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -14,7 +14,8 @@ CREATE TABLE stats_import.test(
 ) WITH (autovacuum_enabled = false);
 SELECT
     pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 18::integer,
         'reltuples', 21::real,
         'relallvisible', 24::integer,
@@ -36,7 +37,7 @@ ORDER BY relname;
  test    |       18 |        21 |            24 |           27
 (1 row)
 
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
  pg_clear_relation_stats 
 -------------------------
  
@@ -45,33 +46,54 @@ SELECT pg_clear_relation_stats('stats_import.test'::regclass);
 --
 -- relstats tests
 --
---- error: relation is wrong type
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid,
+        'relname', 'test',
         'relpages', 17::integer);
-WARNING:  argument "relation" has type "oid", expected type "regclass"
-ERROR:  "relation" cannot be NULL
+ERROR:  "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relpages', 17::integer);
+ERROR:  "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 3.6::float,
+        'relname', 'test',
+        'relpages', 17::integer);
+WARNING:  argument "schemaname" has type "double precision", expected type "text"
+ERROR:  "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relname', 0::oid,
+        'relpages', 17::integer);
+WARNING:  argument "relname" has type "oid", expected type "text"
+ERROR:  "relname" cannot be NULL
 -- error: relation not found
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'nope',
         'relpages', 17::integer);
-ERROR:  could not open relation with OID 0
+WARNING:  Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- error: odd number of variadic arguments cannot be pairs
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible');
 ERROR:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
 -- error: argument name is NULL
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
-        'relation', '0'::oid::regclass,
-        17, '17'::integer);
-ERROR:  name at variadic position 3 has type "integer", expected type "text"
+ERROR:  name at variadic position 5 is NULL
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -84,7 +106,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
 -- regular indexes have special case locking rules
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test_i',
         'relpages', 18::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -132,7 +155,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
 --
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.part_parent_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'part_parent_i',
         'relpages', 2::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -166,7 +190,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
 
 -- ok: set all relstats, with version, no bounds checking
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relpages', '-17'::integer,
         'reltuples', 400::real,
@@ -187,7 +212,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relpages, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '16'::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -204,7 +230,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just reltuples, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'reltuples', '500'::real);
  pg_restore_relation_stats 
 ---------------------------
@@ -221,7 +248,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relallvisible, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible', 5::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -238,7 +266,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: just relallfrozen
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relallfrozen', 3::integer);
  pg_restore_relation_stats 
@@ -256,7 +285,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- warn: bad relpages type, rest updated
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 'nope'::text,
         'reltuples', 400.0::real,
         'relallvisible', 4::integer,
@@ -277,7 +307,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- unrecognized argument name, rest ok
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '171'::integer,
         'nope', 10::integer);
 WARNING:  unrecognized argument name: "nope"
@@ -295,8 +326,7 @@ WHERE oid = 'stats_import.test'::regclass;
 (1 row)
 
 -- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
-    relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
  pg_clear_relation_stats 
 -------------------------
  
@@ -313,87 +343,123 @@ WHERE oid = 'stats_import.test'::regclass;
 -- invalid relkinds for statistics
 CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testseq'::regclass);
+        'schemaname', 'stats_import',
+        'relname', 'testseq');
 ERROR:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
 ERROR:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testview'::regclass);
-ERROR:  cannot modify statistics for relation "testview"
-DETAIL:  This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
 ERROR:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
 --
 -- attribute stats
 --
--- error: object does not exist
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', '0'::oid::regclass,
-    'attname', 'id'::name,
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  could not open relation with OID 0
--- error: relation null
+ERROR:  "schemaname" cannot be NULL
+-- error: schema does not exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', NULL::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'nope',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relation" cannot be NULL
+WARNING:  schema nope does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+ERROR:  "relname" cannot be NULL
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'nope',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+WARNING:  Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', NULL,
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+ERROR:  "relname" cannot be NULL
 -- error: NULL attname
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', NULL::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  must specify either attname or attnum
 -- error: attname doesn't exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'nope'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'nope',
     'inherited', false::boolean,
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "test" does not exist
+ERROR:  column "nope" of relation "stats_import"."test" does not exist
 -- error: both attname and attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  cannot specify both attname and attnum
 -- error: neither attname nor attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  must specify either attname or attnum
 -- error: attribute is system column
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'xmin'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  cannot modify statistics on system column "xmin"
 -- error: inherited null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
 ERROR:  "inherited" cannot be NULL
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'version', 150000::integer,
     'null_frac', 0.2::real,
@@ -421,7 +487,8 @@ AND attname = 'id';
 -- for any stat-having relation.
 --
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.4::real);
@@ -443,8 +510,9 @@ AND attname = 'id';
 
 -- warn: unrecognized argument name, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.2::real,
     'nope', 0.5::real);
@@ -467,8 +535,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 1, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -492,8 +561,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 2, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_vals', '{1,2,3}'::text
@@ -517,8 +587,9 @@ AND attname = 'id';
 
 -- warn: mcf type mismatch, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.22::real,
     'most_common_vals', '{2,1,3}'::text,
@@ -544,8 +615,9 @@ AND attname = 'id';
 
 -- warn: mcv cast failure, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.23::real,
     'most_common_vals', '{2,four,3}'::text,
@@ -570,8 +642,9 @@ AND attname = 'id';
 
 -- ok: mcv+mcf
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'most_common_vals', '{2,1,3}'::text,
     'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -594,8 +667,9 @@ AND attname = 'id';
 
 -- warn: NULL in histogram array, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.24::real,
     'histogram_bounds', '{1,NULL,3,4}'::text
@@ -619,8 +693,9 @@ AND attname = 'id';
 
 -- ok: histogram_bounds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'histogram_bounds', '{1,2,3,4}'::text
     );
@@ -642,8 +717,9 @@ AND attname = 'id';
 
 -- warn: elem_count_histogram null element, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.25::real,
     'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -667,8 +743,9 @@ AND attname = 'tags';
 
 -- ok: elem_count_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.26::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -691,8 +768,9 @@ AND attname = 'tags';
 
 -- warn: range stats on a scalar type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.27::real,
     'range_empty_frac', 0.5::real,
@@ -718,8 +796,9 @@ AND attname = 'id';
 
 -- warn: range_empty_frac range_length_hist null mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.28::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -743,8 +822,9 @@ AND attname = 'arange';
 
 -- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.29::real,
     'range_empty_frac', 0.5::real
@@ -768,8 +848,9 @@ AND attname = 'arange';
 
 -- ok: range_empty_frac + range_length_hist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_empty_frac', 0.5::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -792,8 +873,9 @@ AND attname = 'arange';
 
 -- warn: range bounds histogram on scalar, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.31::real,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -818,8 +900,9 @@ AND attname = 'id';
 
 -- ok: range_bounds_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
     );
@@ -841,8 +924,9 @@ AND attname = 'arange';
 
 -- warn: cannot set most_common_elems for range type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.32::real,
     'most_common_elems', '{3,1}'::text,
@@ -868,8 +952,9 @@ AND attname = 'arange';
 
 -- warn: scalars can't have mcelem, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.33::real,
     'most_common_elems', '{1,3}'::text,
@@ -895,8 +980,9 @@ AND attname = 'id';
 
 -- warn: mcelem / mcelem mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.34::real,
     'most_common_elems', '{one,two}'::text
@@ -920,8 +1006,9 @@ AND attname = 'tags';
 
 -- warn: mcelem / mcelem null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.35::real,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -945,8 +1032,9 @@ AND attname = 'tags';
 
 -- ok: mcelem
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'most_common_elems', '{one,three}'::text,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -969,8 +1057,9 @@ AND attname = 'tags';
 
 -- warn: scalars can't have elem_count_histogram, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.36::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -1022,8 +1111,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
 FROM pg_catalog.pg_stats AS s
 CROSS JOIN LATERAL
     pg_catalog.pg_restore_attribute_stats(
-        'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
-        'attname', s.attname,
+        'schemaname', 'stats_import',
+        'relname', s.tablename::text || '_clone',
+        'attname', s.attname::text,
         'inherited', s.inherited,
         'version', 150000,
         'null_frac', s.null_frac,
@@ -1200,9 +1290,10 @@ AND attname = 'arange';
 (1 row)
 
 SELECT pg_catalog.pg_clear_attribute_stats(
-    relation => 'stats_import.test'::regclass,
-    attname => 'arange'::name,
-    inherited => false::boolean);
+    schemaname => 'stats_import',
+    relname => 'test',
+    attname => 'arange',
+    inherited => false);
  pg_clear_attribute_stats 
 --------------------------
  
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 0ec590688c2..ccdc44e9236 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,7 +17,8 @@ CREATE TABLE stats_import.test(
 
 SELECT
     pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 18::integer,
         'reltuples', 21::real,
         'relallvisible', 24::integer,
@@ -32,37 +33,52 @@ FROM pg_class
 WHERE oid = 'stats_import.test'::regclass
 ORDER BY relname;
 
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
 
 --
 -- relstats tests
 --
 
---- error: relation is wrong type
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid,
+        'relname', 'test',
+        'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 3.6::float,
+        'relname', 'test',
+        'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relname', 0::oid,
         'relpages', 17::integer);
 
 -- error: relation not found
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'nope',
         'relpages', 17::integer);
 
 -- error: odd number of variadic arguments cannot be pairs
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible');
 
 -- error: argument name is NULL
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         NULL, '17'::integer);
 
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
-        'relation', '0'::oid::regclass,
-        17, '17'::integer);
-
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -71,7 +87,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
 -- regular indexes have special case locking rules
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test_i',
         'relpages', 18::integer);
 
 SELECT mode FROM pg_locks
@@ -108,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
 BEGIN;
 
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.part_parent_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'part_parent_i',
         'relpages', 2::integer);
 
 SELECT mode FROM pg_locks
@@ -127,7 +145,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
 
 -- ok: set all relstats, with version, no bounds checking
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relpages', '-17'::integer,
         'reltuples', 400::real,
@@ -140,7 +159,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relpages, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '16'::integer);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -149,7 +169,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just reltuples, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'reltuples', '500'::real);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -158,7 +179,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relallvisible, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible', 5::integer);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,7 +189,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: just relallfrozen
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relallfrozen', 3::integer);
 
@@ -177,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- warn: bad relpages type, rest updated
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 'nope'::text,
         'reltuples', 400.0::real,
         'relallvisible', 4::integer,
@@ -189,7 +213,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- unrecognized argument name, rest ok
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '171'::integer,
         'nope', 10::integer);
 
@@ -198,8 +223,7 @@ FROM pg_class
 WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
-    relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
 
 SELECT relpages, reltuples, relallvisible
 FROM pg_class
@@ -209,48 +233,70 @@ WHERE oid = 'stats_import.test'::regclass;
 CREATE SEQUENCE stats_import.testseq;
 
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testseq'::regclass);
+        'schemaname', 'stats_import',
+        'relname', 'testseq');
 
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
 
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 
-SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
 
 --
 -- attribute stats
 --
 
--- error: object does not exist
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', '0'::oid::regclass,
-    'attname', 'id'::name,
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relation null
+-- error: schema does not exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', NULL::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'nope',
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'nope',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', NULL,
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: NULL attname
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', NULL::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: attname doesn't exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'nope'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'nope',
     'inherited', false::boolean,
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
@@ -258,36 +304,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
 
 -- error: both attname and attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: neither attname nor attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: attribute is system column
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'xmin'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: inherited null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
 
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'version', 150000::integer,
     'null_frac', 0.2::real,
@@ -307,7 +358,8 @@ AND attname = 'id';
 -- for any stat-having relation.
 --
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.4::real);
@@ -321,8 +373,9 @@ AND attname = 'id';
 
 -- warn: unrecognized argument name, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.2::real,
     'nope', 0.5::real);
@@ -336,8 +389,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 1, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -352,8 +406,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 2, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_vals', '{1,2,3}'::text
@@ -368,8 +423,9 @@ AND attname = 'id';
 
 -- warn: mcf type mismatch, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.22::real,
     'most_common_vals', '{2,1,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
 
 -- warn: mcv cast failure, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.23::real,
     'most_common_vals', '{2,four,3}'::text,
@@ -402,8 +459,9 @@ AND attname = 'id';
 
 -- ok: mcv+mcf
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'most_common_vals', '{2,1,3}'::text,
     'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -418,8 +476,9 @@ AND attname = 'id';
 
 -- warn: NULL in histogram array, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.24::real,
     'histogram_bounds', '{1,NULL,3,4}'::text
@@ -434,8 +493,9 @@ AND attname = 'id';
 
 -- ok: histogram_bounds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'histogram_bounds', '{1,2,3,4}'::text
     );
@@ -449,8 +509,9 @@ AND attname = 'id';
 
 -- warn: elem_count_histogram null element, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.25::real,
     'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -465,8 +526,9 @@ AND attname = 'tags';
 
 -- ok: elem_count_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.26::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -481,8 +543,9 @@ AND attname = 'tags';
 
 -- warn: range stats on a scalar type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.27::real,
     'range_empty_frac', 0.5::real,
@@ -498,8 +561,9 @@ AND attname = 'id';
 
 -- warn: range_empty_frac range_length_hist null mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.28::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -514,8 +578,9 @@ AND attname = 'arange';
 
 -- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.29::real,
     'range_empty_frac', 0.5::real
@@ -530,8 +595,9 @@ AND attname = 'arange';
 
 -- ok: range_empty_frac + range_length_hist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_empty_frac', 0.5::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -546,8 +612,9 @@ AND attname = 'arange';
 
 -- warn: range bounds histogram on scalar, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.31::real,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -562,8 +629,9 @@ AND attname = 'id';
 
 -- ok: range_bounds_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
     );
@@ -577,8 +645,9 @@ AND attname = 'arange';
 
 -- warn: cannot set most_common_elems for range type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.32::real,
     'most_common_elems', '{3,1}'::text,
@@ -594,8 +663,9 @@ AND attname = 'arange';
 
 -- warn: scalars can't have mcelem, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.33::real,
     'most_common_elems', '{1,3}'::text,
@@ -611,8 +681,9 @@ AND attname = 'id';
 
 -- warn: mcelem / mcelem mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.34::real,
     'most_common_elems', '{one,two}'::text
@@ -627,8 +698,9 @@ AND attname = 'tags';
 
 -- warn: mcelem / mcelem null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.35::real,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -643,8 +715,9 @@ AND attname = 'tags';
 
 -- ok: mcelem
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'most_common_elems', '{one,three}'::text,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -659,8 +732,9 @@ AND attname = 'tags';
 
 -- warn: scalars can't have elem_count_histogram, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.36::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -707,8 +781,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
 FROM pg_catalog.pg_stats AS s
 CROSS JOIN LATERAL
     pg_catalog.pg_restore_attribute_stats(
-        'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
-        'attname', s.attname,
+        'schemaname', 'stats_import',
+        'relname', s.tablename::text || '_clone',
+        'attname', s.attname::text,
         'inherited', s.inherited,
         'version', 150000,
         'null_frac', s.null_frac,
@@ -853,9 +928,10 @@ AND inherited = false
 AND attname = 'arange';
 
 SELECT pg_catalog.pg_clear_attribute_stats(
-    relation => 'stats_import.test'::regclass,
-    attname => 'arange'::name,
-    inherited => false::boolean);
+    schemaname => 'stats_import',
+    relname => 'test',
+    attname => 'arange',
+    inherited => false);
 
 SELECT COUNT(*)
 FROM pg_stats
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1c3810e1a04..a75e95bc5fd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30365,22 +30365,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <structname>mytable</structname>:
 <programlisting>
  SELECT pg_restore_relation_stats(
-    'relation',  'mytable'::regclass,
-    'relpages',  173::integer,
-    'reltuples', 10000::real);
+    'schemaname', 'myschema',
+    'relname',    'mytable',
+    'relpages',   173::integer,
+    'reltuples',  10000::real);
 </programlisting>
         </para>
         <para>
-         The argument <literal>relation</literal> with a value of type
-         <type>regclass</type> is required, and specifies the table. Other
-         arguments are the names and values of statistics corresponding to
-         certain columns in <link
+         The arguments <literal>schemaname</literal> with a value of type
+         <type>regclass</type> and <literal>relname</literal> are required,
+         and specifies the table. Other arguments are the names and values
+         of statistics corresponding to certain columns in <link
          linkend="catalog-pg-class"><structname>pg_class</structname></link>.
          The currently-supported relation statistics are
          <literal>relpages</literal> with a value of type
          <type>integer</type>, <literal>reltuples</literal> with a value of
-         type <type>real</type>, and <literal>relallvisible</literal> with a
-         value of type <type>integer</type>.
+         type <type>real</type>, <literal>relallvisible</literal> with a
+         value of type <type>integer</type>, and <literal>relallfrozen</literal>
+         with a value of type <type>integer</type>.
         </para>
         <para>
          Additionally, this function accepts argument name
@@ -30408,7 +30410,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <indexterm>
           <primary>pg_clear_relation_stats</primary>
          </indexterm>
-         <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+         <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
          <returnvalue>void</returnvalue>
         </para>
         <para>
@@ -30457,16 +30459,18 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <structname>mytable</structname>:
 <programlisting>
  SELECT pg_restore_attribute_stats(
-    'relation',    'mytable'::regclass,
-    'attname',     'col1'::name,
-    'inherited',   false,
-    'avg_width',   125::integer,
-    'null_frac',   0.5::real);
+    'schemaname', 'myschema',
+    'relname',    'mytable',
+    'attname',    'col1',
+    'inherited',  false,
+    'avg_width',  125::integer,
+    'null_frac',  0.5::real);
 </programlisting>
         </para>
         <para>
-         The required arguments are <literal>relation</literal> with a value
-         of type <type>regclass</type>, which specifies the table; either
+         The required arguments are <literal>schemaname</literal> with a value
+         of type <type>regclass</type> and <literal>relname</literal> with a value
+         of type <type>text</type> which specify the table; either
          <literal>attname</literal> with a value of type <type>name</type> or
          <literal>attnum</literal> with a value of type <type>smallint</type>,
          which specifies the column; and <literal>inherited</literal>, which
@@ -30502,7 +30506,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
           <primary>pg_clear_attribute_stats</primary>
          </indexterm>
          <function>pg_clear_attribute_stats</function> (
-         <parameter>relation</parameter> <type>regclass</type>,
+         <parameter>schemaname</parameter> <type>text</type>,
+         <parameter>relname</parameter> <type>text</type>,
          <parameter>attname</parameter> <type>name</type>,
          <parameter>inherited</parameter> <type>boolean</type> )
          <returnvalue>void</returnvalue>

base-commit: 5eabd91a83adae75f53b61857343660919fef4c7
-- 
2.48.1



  [text/x-patch] v9-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch (29.8K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/6-v9-0002-Downgrade-as-man-pg_restore_-_stats-errors-to-war.patch)
  download | inline diff:
From b5b96d4a2fe4119ef26689cc8f3e1a5b8d24bdda Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v9 2/5] Downgrade as man pg_restore_*_stats errors to
 warnings.

We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
 src/include/statistics/stat_utils.h        |   4 +-
 src/backend/statistics/attribute_stats.c   | 124 +++++++++++-----
 src/backend/statistics/relation_stats.c    |  10 +-
 src/backend/statistics/stat_utils.c        |  51 +++++--
 src/test/regress/expected/stats_import.out | 163 ++++++++++++++++-----
 src/test/regress/sql/stats_import.sql      |  36 ++---
 6 files changed, 277 insertions(+), 111 deletions(-)

diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index cad042c8e4a..298cbae3436 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
 	Oid			argtype;
 };
 
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
 									 struct StatsArgInfo *arginfo,
 									 int argnum);
 extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 								 struct StatsArgInfo *arginfo,
 								 int argnum1, int argnum2);
 
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
 
 extern Oid stats_schema_check_privileges(const char *nspname);
 
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f87db2d6102..4f9bc18f8c6 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
 
 static bool attribute_statistics_update(FunctionCallInfo fcinfo);
 static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
 							   Oid *atttypid, int32 *atttypmod,
 							   char *atttyptype, Oid *atttypcoll,
 							   Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
  * stored as an anyarray, and the representation of the array needs to store
  * the correct element type, which must be derived from the attribute.
  *
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
  */
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -149,8 +151,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	HeapTuple	statup;
 
 	Oid			atttypid = InvalidOid;
-	int32		atttypmod;
-	char		atttyptype;
+	int32		atttypmod = -1;
+	char		atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
 	Oid			atttypcoll = InvalidOid;
 	Oid			eq_opr = InvalidOid;
 	Oid			lt_opr = InvalidOid;
@@ -177,17 +179,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+		return false;
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
-	if (nspoid == InvalidOid)
+	if (!OidIsValid(nspoid))
 		return false;
 
 	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
 	reloid = get_relname_relid(relname, nspoid);
-	if (reloid == InvalidOid)
+	if (!OidIsValid(reloid))
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -196,29 +200,39 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		return false;
+	}
 
 	/* lock before looking up attribute */
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
 		if (!PG_ARGISNULL(ATTNUM_ARG))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
+			return false;
+		}
 		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
 							attname, nspname, relname)));
+			return false;
+		}
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -227,27 +241,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		/* annoyingly, get_attname doesn't check attisdropped */
 		if (attname == NULL ||
 			!SearchSysCacheExistsAttName(reloid, attname))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column %d of relation \"%s\".\"%s\" does not exist",
 							attnum, nspname, relname)));
+			return false;
+		}
 	}
 	else
 	{
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("must specify either attname or attnum")));
-		attname = NULL;			/* keep compiler quiet */
-		attnum = 0;
+		return false;
 	}
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
+		return false;
+	}
 
-	stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+		return false;
 	inherited = PG_GETARG_BOOL(INHERITED_ARG);
 
 	/*
@@ -296,10 +316,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* derive information from attribute */
-	get_attr_stat_type(reloid, attnum,
-					   &atttypid, &atttypmod,
-					   &atttyptype, &atttypcoll,
-					   &eq_opr, &lt_opr);
+	if (!get_attr_stat_type(reloid, attnum,
+							&atttypid, &atttypmod,
+							&atttyptype, &atttypcoll,
+							&eq_opr, &lt_opr))
+		result = false;
 
 	/* if needed, derive element type */
 	if (do_mcelem || do_dechist)
@@ -579,7 +600,7 @@ get_attr_expr(Relation rel, int attnum)
 /*
  * Derive type information from the attribute.
  */
-static void
+static bool
 get_attr_stat_type(Oid reloid, AttrNumber attnum,
 				   Oid *atttypid, int32 *atttypmod,
 				   char *atttyptype, Oid *atttypcoll,
@@ -596,18 +617,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 
 	/* Attribute not found */
 	if (!HeapTupleIsValid(atup))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	attr = (Form_pg_attribute) GETSTRUCT(atup);
 
 	if (attr->attisdropped)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	expr = get_attr_expr(rel, attr->attnum);
 
@@ -656,6 +685,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 		*atttypcoll = DEFAULT_COLLATION_OID;
 
 	relation_close(rel, NoLock);
+	return true;
 }
 
 /*
@@ -781,6 +811,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
 	if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
 		slotidx = first_empty;
 
+	/*
+	 * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+	 * statistic kinds, so this can safely remain an ERROR for now.
+	 */
 	if (slotidx >= STATISTIC_NUM_SLOTS)
 		ereport(ERROR,
 				(errmsg("maximum number of statistics slots exceeded: %d",
@@ -927,15 +961,19 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+		PG_RETURN_VOID();
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
 	if (!OidIsValid(nspoid))
-		return false;
+		PG_RETURN_VOID();
 
 	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
 	reloid = get_relname_relid(relname, nspoid);
@@ -944,31 +982,41 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
-		return false;
+		PG_RETURN_VOID();
 	}
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		PG_RETURN_VOID();
+	}
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		PG_RETURN_VOID();
 
 	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
 	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
 						attname)));
+		PG_RETURN_VOID();
+	}
 
 	if (attnum == InvalidAttrNumber)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
 						attname, get_rel_name(reloid))));
+		PG_RETURN_VOID();
+	}
 
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index fdc69bc93e2..49109cf721d 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -84,8 +84,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
-	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+		return false;
+
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
 	nspoid = stats_schema_check_privileges(nspname);
@@ -108,7 +111,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e037d4994e8..dd9d88ac1c5 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -34,16 +34,20 @@
 /*
  * Ensure that a given argument is not null.
  */
-void
+bool
 stats_check_required_arg(FunctionCallInfo fcinfo,
 						 struct StatsArgInfo *arginfo,
 						 int argnum)
 {
 	if (PG_ARGISNULL(argnum))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("\"%s\" cannot be NULL",
 						arginfo[argnum].argname)));
+		return false;
+	}
+	return true;
 }
 
 /*
@@ -128,13 +132,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
  *   - the role owns the current database and the relation is not shared
  *   - the role has the MAINTAIN privilege on the relation
  */
-void
+bool
 stats_lock_check_privileges(Oid reloid)
 {
 	Relation	table;
 	Oid			table_oid = reloid;
 	Oid			index_oid = InvalidOid;
 	LOCKMODE	index_lockmode = NoLock;
+	bool		ok = true;
 
 	/*
 	 * For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -174,14 +179,15 @@ stats_lock_check_privileges(Oid reloid)
 		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
-			ereport(ERROR,
+			ereport(WARNING,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot modify statistics for relation \"%s\"",
 							RelationGetRelationName(table)),
 					 errdetail_relkind_not_supported(table->rd_rel->relkind)));
+		ok = false;
 	}
 
-	if (OidIsValid(index_oid))
+	if (ok && (OidIsValid(index_oid)))
 	{
 		Relation	index;
 
@@ -194,25 +200,33 @@ stats_lock_check_privileges(Oid reloid)
 		relation_close(index, NoLock);
 	}
 
-	if (table->rd_rel->relisshared)
-		ereport(ERROR,
+	if (ok && (table->rd_rel->relisshared))
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics for shared relation")));
+		ok = false;
+	}
 
-	if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+	if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
 	{
 		AclResult	aclresult = pg_class_aclcheck(RelationGetRelid(table),
 												  GetUserId(),
 												  ACL_MAINTAIN);
 
 		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult,
-						   get_relkind_objtype(table->rd_rel->relkind),
-						   NameStr(table->rd_rel->relname));
+		{
+			ereport(WARNING,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						errmsg("permission denied for relation %s",
+							   NameStr(table->rd_rel->relname))));
+			ok = false;
+		}
 	}
 
 	/* retain lock on table */
 	relation_close(table, NoLock);
+	return ok;
 }
 
 
@@ -318,9 +332,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 								  &args, &types, &argnulls);
 
 	if (nargs % 2 != 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				errmsg("variadic arguments must be name/value pairs"),
 				errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+		return false;
+	}
 
 	/*
 	 * For each argument name/value pair, find corresponding positional
@@ -333,14 +350,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 		char	   *argname;
 
 		if (argnulls[i])
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d is NULL", i + 1)));
+			return false;
+		}
 
 		if (types[i] != TEXTOID)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
 							i + 1, format_type_be(types[i]),
 							format_type_be(TEXTOID))));
+			return false;
+		}
 
 		if (argnulls[i + 1])
 			continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 2f1295f2149..6551d6bf099 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,31 +46,51 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 --
 -- relstats tests
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
-ERROR:  "schemaname" cannot be NULL
--- error: relname missing
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
-ERROR:  "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 WARNING:  argument "schemaname" has type "double precision", expected type "text"
-ERROR:  "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 WARNING:  argument "relname" has type "oid", expected type "text"
-ERROR:  "relname" cannot be NULL
--- error: relation not found
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
@@ -81,19 +101,30 @@ WARNING:  Relation "stats_import"."nope" not found.
  f
 (1 row)
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
-ERROR:  variadic arguments must be name/value pairs
+WARNING:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 5 is NULL
+WARNING:  name at variadic position 5 is NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -345,26 +376,46 @@ CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR:  cannot modify statistics for relation "testview"
+WARNING:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 --
 -- attribute stats
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -377,14 +428,19 @@ WARNING:  schema nope does not exist
  f
 (1 row)
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: relname does not exist
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -397,23 +453,33 @@ WARNING:  Relation "stats_import"."nope" not found.
  f
 (1 row)
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: NULL attname
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attname doesn't exist
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -422,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "stats_import"."test" does not exist
--- error: both attname and attnum
+WARNING:  column "nope" of relation "stats_import"."test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -431,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING:  cannot specify both attname and attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attribute is system column
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING:  cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
-ERROR:  "inherited" cannot be NULL
+WARNING:  "inherited" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index ccdc44e9236..dbbebce1673 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 -- relstats tests
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
 
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 
--- error: relation not found
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
 
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
 -- attribute stats
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: NULL attname
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
 
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: inherited null
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
-- 
2.48.1



  [text/x-patch] v9-0004-Batching-getAttributeStats.patch (21.6K, ../../CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com/7-v9-0004-Batching-getAttributeStats.patch)
  download | inline diff:
From d9ab97b8ca00d03ab370de5b99546d01e0480bd5 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v9 4/5] Batching getAttributeStats().

The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.

The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
 src/bin/pg_dump/pg_dump.c | 556 ++++++++++++++++++++++++++------------
 1 file changed, 385 insertions(+), 171 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 38ba6a90106..0c26dc7a1b4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
 	zeroAsNone = 4,
 } OidOptions;
 
+typedef enum StatsBufferState
+{
+	STATSBUF_UNINITIALIZED = 0,
+	STATSBUF_ACTIVE,
+	STATSBUF_EXHAUSTED
+}			StatsBufferState;
+
+typedef struct
+{
+	PGresult   *res;			/* results from most recent
+								 * getAttributeStats() */
+	int			idx;			/* first un-consumed row of results */
+	TocEntry   *te;				/* next TOC entry to search for statsitics
+								 * data */
+
+	StatsBufferState state;		/* current state of the buffer */
+}			AttributeStatsBuffer;
+
+
 /* global decls */
 static bool dosync = true;		/* Issue fsync() to make dump durable on disk. */
 
@@ -209,6 +228,18 @@ static int	nbinaryUpgradeClassOids = 0;
 static SequenceItem *sequences = NULL;
 static int	nsequences = 0;
 
+static AttributeStatsBuffer attrstats =
+{
+	NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
 /*
  * The default number of rows per INSERT when
  * --inserts is specified without --rows-per-insert
@@ -222,6 +253,10 @@ static int	nsequences = 0;
  */
 #define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
 
+
+
+/* TODO: fmtId(const char *rawid) */
+
 /*
  * Macro for producing quoted, schema-qualified name of a dumpable object.
  */
@@ -399,6 +434,9 @@ static void setupDumpWorker(Archive *AH);
 static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
 static bool forcePartitionRootLoad(const TableInfo *tbinfo);
 static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+								const char *argname, const char *argtype,
+								const char *argval);
 
 
 int
@@ -10477,7 +10515,286 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
 }
 
 /*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData schemas;
+	PQExpBufferData relations;
+	int			numoids = 0;
+
+	Assert(AH != NULL);
+
+	/* free last result set, if any */
+	if (attrstats.state == STATSBUF_ACTIVE)
+		PQclear(attrstats.res);
+
+	/* If we have looped around to the start of the TOC, restart */
+	if (attrstats.te == AH->toc)
+		attrstats.te = AH->toc->next;
+
+	initPQExpBuffer(&schemas);
+	initPQExpBuffer(&relations);
+
+	/*
+	 * Walk ahead looking for relstats entries that are active in this
+	 * section, adding the names to the schemas and relations lists.
+	 */
+	while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+	{
+		if (attrstats.te->reqs != 0 &&
+			strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+		{
+			RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+			Assert(rsinfo != NULL);
+
+			if (numoids > 0)
+			{
+				appendPQExpBufferStr(&schemas, ",");
+				appendPQExpBufferStr(&relations, ",");
+			}
+			appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+			numoids++;
+		}
+
+		attrstats.te = attrstats.te->next;
+	}
+
+	if (numoids > 0)
+	{
+		PQExpBufferData query;
+
+		initPQExpBuffer(&query);
+		appendPQExpBuffer(&query,
+						  "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+						  schemas.data, relations.data);
+		attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+		attrstats.idx = 0;
+	}
+	else
+	{
+		attrstats.state = STATSBUF_EXHAUSTED;
+		attrstats.res = NULL;
+		attrstats.idx = -1;
+	}
+
+	termPQExpBuffer(&schemas);
+	termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData query;
+
+	Assert(AH != NULL);
+	initPQExpBuffer(&query);
+
+	appendPQExpBufferStr(&query,
+						 "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+						 "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+						 "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+						 "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+						 "s.most_common_elems, s.most_common_elem_freqs, "
+						 "s.elem_count_histogram, ");
+
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(&query,
+							 "s.range_length_histogram, "
+							 "s.range_empty_frac, "
+							 "s.range_bounds_histogram ");
+	else
+		appendPQExpBufferStr(&query,
+							 "NULL AS range_length_histogram, "
+							 "NULL AS range_empty_frac, "
+							 " NULL AS range_bounds_histogram ");
+
+	/*
+	 * The results must be in the order of relations supplied in the
+	 * parameters to ensure that they are in sync with a walk of the TOC.
+	 *
+	 * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+	 * is a way to lead the query into using the index
+	 * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+	 * expensive full scan of pg_stats.
+	 *
+	 * We may need to adjust this query for versions that are not so easily
+	 * led.
+	 */
+	appendPQExpBufferStr(&query,
+						 "FROM pg_catalog.pg_stats AS s "
+						 "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+						 "ON s.schemaname = u.schemaname "
+						 "AND s.tablename = u.tablename "
+						 "WHERE s.tablename = ANY($2) "
+						 "ORDER BY u.ord, s.attname, s.inherited");
+
+	ExecuteSqlStatement(fout, query.data);
+
+	termPQExpBuffer(&query);
+
+	attrstats.te = AH->toc->next;
+
+	fetchNextAttributeStats(fout);
+
+	attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+					 const RelStatsInfo *rsinfo)
+{
+	PGresult   *res = attrstats.res;
+	int			tup_num = attrstats.idx;
+
+	const char *attname;
+
+	static bool indexes_set = false;
+	static int	i_attname,
+				i_inherited,
+				i_null_frac,
+				i_avg_width,
+				i_n_distinct,
+				i_most_common_vals,
+				i_most_common_freqs,
+				i_histogram_bounds,
+				i_correlation,
+				i_most_common_elems,
+				i_most_common_elem_freqs,
+				i_elem_count_histogram,
+				i_range_length_histogram,
+				i_range_empty_frac,
+				i_range_bounds_histogram;
+
+	if (!indexes_set)
+	{
+		/*
+		 * It's a prepared statement, so the indexes will be the same for all
+		 * result sets, so we only need to set them once.
+		 */
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		indexes_set = true;
+	}
+
+	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+					  fout->remoteVersion);
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+	if (PQgetisnull(res, tup_num, i_attname))
+		pg_fatal("attname cannot be NULL");
+	attname = PQgetvalue(res, tup_num, i_attname);
+
+	/*
+	 * Indexes look up attname in indAttNames to derive attnum, all others use
+	 * attname directly.  We must specify attnum for indexes, since their
+	 * attnames are not necessarily stable across dump/reload.
+	 */
+	if (rsinfo->nindAttNames == 0)
+	{
+		appendPQExpBuffer(out, ",\n\t'attname', ");
+		appendStringLiteralAH(out, attname, fout);
+	}
+	else
+	{
+		bool		found = false;
+
+		for (int i = 0; i < rsinfo->nindAttNames; i++)
+			if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+			{
+				appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+								  i + 1);
+				found = true;
+				break;
+			}
+
+		if (!found)
+			pg_fatal("could not find index attname \"%s\"", attname);
+	}
+
+	if (!PQgetisnull(res, tup_num, i_inherited))
+		appendNamedArgument(out, fout, "inherited", "boolean",
+							PQgetvalue(res, tup_num, i_inherited));
+	if (!PQgetisnull(res, tup_num, i_null_frac))
+		appendNamedArgument(out, fout, "null_frac", "real",
+							PQgetvalue(res, tup_num, i_null_frac));
+	if (!PQgetisnull(res, tup_num, i_avg_width))
+		appendNamedArgument(out, fout, "avg_width", "integer",
+							PQgetvalue(res, tup_num, i_avg_width));
+	if (!PQgetisnull(res, tup_num, i_n_distinct))
+		appendNamedArgument(out, fout, "n_distinct", "real",
+							PQgetvalue(res, tup_num, i_n_distinct));
+	if (!PQgetisnull(res, tup_num, i_most_common_vals))
+		appendNamedArgument(out, fout, "most_common_vals", "text",
+							PQgetvalue(res, tup_num, i_most_common_vals));
+	if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+		appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_freqs));
+	if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+		appendNamedArgument(out, fout, "histogram_bounds", "text",
+							PQgetvalue(res, tup_num, i_histogram_bounds));
+	if (!PQgetisnull(res, tup_num, i_correlation))
+		appendNamedArgument(out, fout, "correlation", "real",
+							PQgetvalue(res, tup_num, i_correlation));
+	if (!PQgetisnull(res, tup_num, i_most_common_elems))
+		appendNamedArgument(out, fout, "most_common_elems", "text",
+							PQgetvalue(res, tup_num, i_most_common_elems));
+	if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+		appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+	if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+		appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+							PQgetvalue(res, tup_num, i_elem_count_histogram));
+	if (fout->remoteVersion >= 170000)
+	{
+		if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+			appendNamedArgument(out, fout, "range_length_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_length_histogram));
+		if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+			appendNamedArgument(out, fout, "range_empty_frac", "real",
+								PQgetvalue(res, tup_num, i_range_empty_frac));
+		if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+			appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_bounds_histogram));
+	}
+	appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
  *
  * Generate the SQL statements needed to restore a relation's statistics.
  */
@@ -10485,64 +10802,21 @@ static char *
 printRelationStats(Archive *fout, const void *userArg)
 {
 	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
-	const DumpableObject *dobj = &rsinfo->dobj;
+	const DumpableObject *dobj;
+	const char *relschema;
+	const char *relname;
+
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
 
-	PQExpBufferData query;
 	PQExpBufferData out;
 
-	PGresult   *res;
-
-	static bool first_query = true;
-	static int	i_attname;
-	static int	i_inherited;
-	static int	i_null_frac;
-	static int	i_avg_width;
-	static int	i_n_distinct;
-	static int	i_most_common_vals;
-	static int	i_most_common_freqs;
-	static int	i_histogram_bounds;
-	static int	i_correlation;
-	static int	i_most_common_elems;
-	static int	i_most_common_elem_freqs;
-	static int	i_elem_count_histogram;
-	static int	i_range_length_histogram;
-	static int	i_range_empty_frac;
-	static int	i_range_bounds_histogram;
-
-	initPQExpBuffer(&query);
-
-	if (first_query)
-	{
-		appendPQExpBufferStr(&query,
-							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
-							 "SELECT s.attname, s.inherited, "
-							 "s.null_frac, s.avg_width, s.n_distinct, "
-							 "s.most_common_vals, s.most_common_freqs, "
-							 "s.histogram_bounds, s.correlation, "
-							 "s.most_common_elems, s.most_common_elem_freqs, "
-							 "s.elem_count_histogram, ");
-
-		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(&query,
-								 "s.range_length_histogram, "
-								 "s.range_empty_frac, "
-								 "s.range_bounds_histogram ");
-		else
-			appendPQExpBufferStr(&query,
-								 "NULL AS range_length_histogram,"
-								 "NULL AS range_empty_frac,"
-								 "NULL AS range_bounds_histogram ");
-
-		appendPQExpBufferStr(&query,
-							 "FROM pg_catalog.pg_stats s "
-							 "WHERE s.schemaname = $1 "
-							 "AND s.tablename = $2 "
-							 "ORDER BY s.attname, s.inherited");
-
-		ExecuteSqlStatement(fout, query.data);
-
-		resetPQExpBuffer(&query);
-	}
+	Assert(rsinfo != NULL);
+	dobj = &rsinfo->dobj;
+	Assert(dobj != NULL);
+	relschema = dobj->namespace->dobj.name;
+	Assert(relschema != NULL);
+	relname = dobj->name;
+	Assert(relname != NULL);
 
 	initPQExpBuffer(&out);
 
@@ -10561,132 +10835,72 @@ printRelationStats(Archive *fout, const void *userArg)
 	appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
 					  rsinfo->relallvisible);
 
-	/* fetch attribute stats */
-	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(&query, ", ");
-	appendStringLiteralAH(&query, dobj->name, fout);
-	appendPQExpBufferStr(&query, ")");
+	AH->txnCount++;
 
-	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+	if (attrstats.state == STATSBUF_UNINITIALIZED)
+		initAttributeStats(fout);
 
-	if (first_query)
+	/*
+	 * Because the query returns rows in the same order as the relations
+	 * requested, and because every relation gets at least one row in the
+	 * result set, the first row for this relation must correspond either to
+	 * the current row of this result set (if one exists) or the first row of
+	 * the next result set (if this one is already consumed).
+	 */
+	if (attrstats.state != STATSBUF_ACTIVE)
+		pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+				 rsinfo->dobj.namespace->dobj.name,
+				 rsinfo->dobj.name);
+
+	/*
+	 * If the current result set has been fully consumed, then the row(s) we
+	 * need (if any) would be found in the next one. This will update
+	 * attrstats.res and attrstats.idx.
+	 */
+	if (PQntuples(attrstats.res) <= attrstats.idx)
+		fetchNextAttributeStats(fout);
+
+	while (true)
 	{
-		i_attname = PQfnumber(res, "attname");
-		i_inherited = PQfnumber(res, "inherited");
-		i_null_frac = PQfnumber(res, "null_frac");
-		i_avg_width = PQfnumber(res, "avg_width");
-		i_n_distinct = PQfnumber(res, "n_distinct");
-		i_most_common_vals = PQfnumber(res, "most_common_vals");
-		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-		i_correlation = PQfnumber(res, "correlation");
-		i_most_common_elems = PQfnumber(res, "most_common_elems");
-		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
-		first_query = false;
-	}
-
-	/* restore attribute stats */
-	for (int rownum = 0; rownum < PQntuples(res); rownum++)
-	{
-		const char *attname;
-
-		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
-						  fout->remoteVersion);
-		appendPQExpBufferStr(&out, "\t'schemaname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(&out, ",\n\t'relname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
-		if (PQgetisnull(res, rownum, i_attname))
-			pg_fatal("attname cannot be NULL");
-		attname = PQgetvalue(res, rownum, i_attname);
+		int			i_schemaname;
+		int			i_tablename;
+		char	   *schemaname;
+		char	   *tablename;	/* misnomer, following pg_stats naming */
 
 		/*
-		 * Indexes look up attname in indAttNames to derive attnum, all others
-		 * use attname directly.  We must specify attnum for indexes, since
-		 * their attnames are not necessarily stable across dump/reload.
+		 * If we hit the end of the result set, then there are no more records
+		 * for this relation, so we should stop, but first get the next result
+		 * set for the next batch of relations.
 		 */
-		if (rsinfo->nindAttNames == 0)
+		if (PQntuples(attrstats.res) <= attrstats.idx)
 		{
-			appendPQExpBuffer(&out, ",\n\t'attname', ");
-			appendStringLiteralAH(&out, attname, fout);
-		}
-		else
-		{
-			bool		found = false;
-
-			for (int i = 0; i < rsinfo->nindAttNames; i++)
-			{
-				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
-				{
-					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
-									  i + 1);
-					found = true;
-					break;
-				}
-			}
-
-			if (!found)
-				pg_fatal("could not find index attname \"%s\"", attname);
+			fetchNextAttributeStats(fout);
+			break;
 		}
 
-		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(&out, fout, "inherited", "boolean",
-								PQgetvalue(res, rownum, i_inherited));
-		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(&out, fout, "null_frac", "real",
-								PQgetvalue(res, rownum, i_null_frac));
-		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(&out, fout, "avg_width", "integer",
-								PQgetvalue(res, rownum, i_avg_width));
-		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(&out, fout, "n_distinct", "real",
-								PQgetvalue(res, rownum, i_n_distinct));
-		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(&out, fout, "most_common_vals", "text",
-								PQgetvalue(res, rownum, i_most_common_vals));
-		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_freqs));
-		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(&out, fout, "histogram_bounds", "text",
-								PQgetvalue(res, rownum, i_histogram_bounds));
-		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(&out, fout, "correlation", "real",
-								PQgetvalue(res, rownum, i_correlation));
-		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(&out, fout, "most_common_elems", "text",
-								PQgetvalue(res, rownum, i_most_common_elems));
-		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_elem_freqs));
-		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
-								PQgetvalue(res, rownum, i_elem_count_histogram));
-		if (fout->remoteVersion >= 170000)
-		{
-			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(&out, fout, "range_length_histogram", "text",
-									PQgetvalue(res, rownum, i_range_length_histogram));
-			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(&out, fout, "range_empty_frac", "real",
-									PQgetvalue(res, rownum, i_range_empty_frac));
-			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
-									PQgetvalue(res, rownum, i_range_bounds_histogram));
-		}
-		appendPQExpBufferStr(&out, "\n);\n");
+		i_schemaname = PQfnumber(attrstats.res, "schemaname");
+		Assert(i_schemaname >= 0);
+		i_tablename = PQfnumber(attrstats.res, "tablename");
+		Assert(i_tablename >= 0);
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+			pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+			pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+		schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+		tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+		/* stop if current stat row isn't for this relation */
+		if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+			break;
+
+		appendAttributeStats(fout, &out, rsinfo);
+		AH->txnCount++;
+		attrstats.idx++;
 	}
 
-	PQclear(res);
-
-	termPQExpBuffer(&query);
 	return out.data;
 }
 
-- 
2.48.1



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-19 22:17                   ` Jeff Davis <[email protected]>
  2025-03-19 22:35                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Jeff Davis @ 2025-03-19 22:17 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Sat, 2025-03-15 at 21:37 -0400, Corey Huinker wrote:
> > 0001 - no changes, but the longer I go the more I'm certain this is
> > something we want to do.

This replaces regclassin with custom lookups of the namespace and
relname, but misses some of the complexities that regclassin is
handling. For instance, it calls RangeVarGetRelid(), which calls
LookupExplicitNamespace(), which handles temp tables and
InvokeNamespaceSearchHook().

At first it looked like a bit too much code to copy, but regclassin()
passes NoLock, which means we basically just have to call
LookupExplicitNamespace().

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-19 22:35                     ` Corey Huinker <[email protected]>
  1 sibling, 0 replies; 87+ messages in thread

From: Corey Huinker @ 2025-03-19 22:35 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> This replaces regclassin with custom lookups of the namespace and
> relname, but misses some of the complexities that regclassin is
> handling. For instance, it calls RangeVarGetRelid(), which calls
> LookupExplicitNamespace(), which handles temp tables and
> InvokeNamespaceSearchHook().
>
> At first it looked like a bit too much code to copy, but regclassin()
> passes NoLock, which means we basically just have to call
> LookupExplicitNamespace().


To be clear, LookupExplicitNamespace() can call aclcheck_error(), which is
something we cannot presently step-down into a WARNING, so an aclcheck
failure inside a restore/upgrade would fail the upgrade. I want to make
sure we can live with that because it might be hard to explain what's an
error we can nerf and what isn't.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-25 06:53                     ` Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-25 06:53 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Wed, 2025-03-19 at 15:17 -0700, Jeff Davis wrote:
> On Sat, 2025-03-15 at 21:37 -0400, Corey Huinker wrote:
> > > 0001 - no changes, but the longer I go the more I'm certain this
> > > is
> > > something we want to do.
> 
> This replaces regclassin with custom lookups of the namespace and
> relname, but misses some of the complexities that regclassin is
> handling. For instance, it calls RangeVarGetRelid(), which calls
> LookupExplicitNamespace(), which handles temp tables and
> InvokeNamespaceSearchHook().
> 
> At first it looked like a bit too much code to copy, but regclassin()
> passes NoLock, which means we basically just have to call
> LookupExplicitNamespace().

Attached new version 9j:

* Changed to use LookupExplicitNamespace()
* Added test for temp tables
* Doc fixes

Regards,
	Jeff Davis



Attachments:

  [text/x-patch] v9j-0001-Stats-use-schemaname-relname-instead-of-regclass.patch (68.4K, ../../[email protected]/2-v9j-0001-Stats-use-schemaname-relname-instead-of-regclass.patch)
  download | inline diff:
From 72d4b9fc128e6d4ef73bb24ebba41797d06a7d9e Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Mar 2025 22:16:52 -0500
Subject: [PATCH v9j] Stats: use schemaname/relname instead of regclass.

For import and export, use schemaname/relname rather than
regclass.

This is more natural during export, fits with the other arguments
better, and it gives better control over error handling in case we
need to downgrade more errors to warnings.

Also, use text for the argument types for schemaname, relname, and
attname so that casts to "name" are not required.

Author: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=ceOSsx_=oe73QQ-BxUFR2Cwqum7-UP_fPe22DBY0NerA@mail.gmail.com
---
 doc/src/sgml/func.sgml                     |  50 +--
 src/backend/statistics/attribute_stats.c   |  87 +++--
 src/backend/statistics/relation_stats.c    |  65 ++--
 src/backend/statistics/stat_utils.c        |  37 +++
 src/bin/pg_dump/pg_dump.c                  |  25 +-
 src/bin/pg_dump/t/002_pg_dump.pl           |   6 +-
 src/include/catalog/catversion.h           |   2 +-
 src/include/catalog/pg_proc.dat            |   8 +-
 src/include/statistics/stat_utils.h        |   2 +
 src/test/regress/expected/stats_import.out | 353 ++++++++++++++-------
 src/test/regress/sql/stats_import.sql      | 306 ++++++++++++------
 11 files changed, 647 insertions(+), 294 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6fa1d6586b8..f8c1deb04ee 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30364,22 +30364,24 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <structname>mytable</structname>:
 <programlisting>
  SELECT pg_restore_relation_stats(
-    'relation',  'mytable'::regclass,
-    'relpages',  173::integer,
-    'reltuples', 10000::real);
+    'schemaname', 'myschema',
+    'relname',    'mytable',
+    'relpages',   173::integer,
+    'reltuples',  10000::real);
 </programlisting>
         </para>
         <para>
-         The argument <literal>relation</literal> with a value of type
-         <type>regclass</type> is required, and specifies the table. Other
+         The arguments <literal>schemaname</literal> and
+         <literal>relname</literal> are required, and specify the table. Other
          arguments are the names and values of statistics corresponding to
          certain columns in <link
          linkend="catalog-pg-class"><structname>pg_class</structname></link>.
          The currently-supported relation statistics are
          <literal>relpages</literal> with a value of type
          <type>integer</type>, <literal>reltuples</literal> with a value of
-         type <type>real</type>, and <literal>relallvisible</literal> with a
-         value of type <type>integer</type>.
+         type <type>real</type>, <literal>relallvisible</literal> with a value
+         of type <type>integer</type>, and <literal>relallfrozen</literal>
+         with a value of type <type>integer</type>.
         </para>
         <para>
          Additionally, this function accepts argument name
@@ -30407,7 +30409,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <indexterm>
           <primary>pg_clear_relation_stats</primary>
          </indexterm>
-         <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+         <function>pg_clear_relation_stats</function> ( <parameter>schemaname</parameter> <type>text</type>, <parameter>relname</parameter> <type>text</type> )
          <returnvalue>void</returnvalue>
         </para>
         <para>
@@ -30456,22 +30458,23 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
          <structname>mytable</structname>:
 <programlisting>
  SELECT pg_restore_attribute_stats(
-    'relation',    'mytable'::regclass,
-    'attname',     'col1'::name,
-    'inherited',   false,
-    'avg_width',   125::integer,
-    'null_frac',   0.5::real);
+    'schemaname', 'myschema',
+    'relname',    'mytable',
+    'attname',    'col1',
+    'inherited',  false,
+    'avg_width',  125::integer,
+    'null_frac',  0.5::real);
 </programlisting>
         </para>
         <para>
-         The required arguments are <literal>relation</literal> with a value
-         of type <type>regclass</type>, which specifies the table; either
-         <literal>attname</literal> with a value of type <type>name</type> or
-         <literal>attnum</literal> with a value of type <type>smallint</type>,
-         which specifies the column; and <literal>inherited</literal>, which
-         specifies whether the statistics include values from child tables.
-         Other arguments are the names and values of statistics corresponding
-         to columns in <link
+         The required arguments are <literal>schemaname</literal> and
+         <literal>relname</literal> with a value of type <type>text</type>
+         which specify the table; either <literal>attname</literal> with a
+         value of type <type>text</type> or <literal>attnum</literal> with a
+         value of type <type>smallint</type>, which specifies the column; and
+         <literal>inherited</literal>, which specifies whether the statistics
+         include values from child tables.  Other arguments are the names and
+         values of statistics corresponding to columns in <link
          linkend="view-pg-stats"><structname>pg_stats</structname></link>.
         </para>
         <para>
@@ -30501,8 +30504,9 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
           <primary>pg_clear_attribute_stats</primary>
          </indexterm>
          <function>pg_clear_attribute_stats</function> (
-         <parameter>relation</parameter> <type>regclass</type>,
-         <parameter>attname</parameter> <type>name</type>,
+         <parameter>schemaname</parameter> <type>text</type>,
+         <parameter>relname</parameter> <type>text</type>,
+         <parameter>attname</parameter> <type>text</type>,
          <parameter>inherited</parameter> <type>boolean</type> )
          <returnvalue>void</returnvalue>
         </para>
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 6bcbee0edba..f87db2d6102 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -36,7 +36,8 @@
 
 enum attribute_stats_argnum
 {
-	ATTRELATION_ARG = 0,
+	ATTRELSCHEMA_ARG = 0,
+	ATTRELNAME_ARG,
 	ATTNAME_ARG,
 	ATTNUM_ARG,
 	INHERITED_ARG,
@@ -58,8 +59,9 @@ enum attribute_stats_argnum
 
 static struct StatsArgInfo attarginfo[] =
 {
-	[ATTRELATION_ARG] = {"relation", REGCLASSOID},
-	[ATTNAME_ARG] = {"attname", NAMEOID},
+	[ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
+	[ATTRELNAME_ARG] = {"relname", TEXTOID},
+	[ATTNAME_ARG] = {"attname", TEXTOID},
 	[ATTNUM_ARG] = {"attnum", INT2OID},
 	[INHERITED_ARG] = {"inherited", BOOLOID},
 	[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
@@ -80,7 +82,8 @@ static struct StatsArgInfo attarginfo[] =
 
 enum clear_attribute_stats_argnum
 {
-	C_ATTRELATION_ARG = 0,
+	C_ATTRELSCHEMA_ARG = 0,
+	C_ATTRELNAME_ARG,
 	C_ATTNAME_ARG,
 	C_INHERITED_ARG,
 	C_NUM_ATTRIBUTE_STATS_ARGS
@@ -88,8 +91,9 @@ enum clear_attribute_stats_argnum
 
 static struct StatsArgInfo cleararginfo[] =
 {
-	[C_ATTRELATION_ARG] = {"relation", REGCLASSOID},
-	[C_ATTNAME_ARG] = {"attname", NAMEOID},
+	[C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
+	[C_ATTRELNAME_ARG] = {"relation", TEXTOID},
+	[C_ATTNAME_ARG] = {"attname", TEXTOID},
 	[C_INHERITED_ARG] = {"inherited", BOOLOID},
 	[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
 };
@@ -133,6 +137,9 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
 {
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
 	char	   *attname;
 	AttrNumber	attnum;
@@ -170,8 +177,23 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
-	reloid = PG_GETARG_OID(ATTRELATION_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (nspoid == InvalidOid)
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (reloid == InvalidOid)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -185,21 +207,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
-		Name		attnamename;
-
 		if (!PG_ARGISNULL(ATTNUM_ARG))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
-		attnamename = PG_GETARG_NAME(ATTNAME_ARG);
-		attname = NameStr(*attnamename);
+		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column \"%s\" of relation \"%s\" does not exist",
-							attname, get_rel_name(reloid))));
+					 errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist",
+							attname, nspname, relname)));
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -210,8 +229,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 			!SearchSysCacheExistsAttName(reloid, attname))
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column %d of relation \"%s\" does not exist",
-							attnum, get_rel_name(reloid))));
+					 errmsg("column %d of relation \"%s\".\"%s\" does not exist",
+							attnum, nspname, relname)));
 	}
 	else
 	{
@@ -900,13 +919,33 @@ init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
 Datum
 pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 {
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
-	Name		attname;
+	char	   *attname;
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
-	reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (!OidIsValid(nspoid))
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (!OidIsValid(reloid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -916,23 +955,21 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 
 	stats_lock_check_privileges(reloid);
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	attname = PG_GETARG_NAME(C_ATTNAME_ARG);
-	attnum = get_attnum(reloid, NameStr(*attname));
+	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
-						NameStr(*attname))));
+						attname)));
 
 	if (attnum == InvalidAttrNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
-						NameStr(*attname), get_rel_name(reloid))));
+						attname, get_rel_name(reloid))));
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
 	delete_pg_statistic(reloid, attnum, inherited);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 52dfa477187..fdc69bc93e2 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -19,9 +19,12 @@
 
 #include "access/heapam.h"
 #include "catalog/indexing.h"
+#include "catalog/namespace.h"
 #include "statistics/stat_utils.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 
@@ -32,7 +35,8 @@
 
 enum relation_stats_argnum
 {
-	RELATION_ARG = 0,
+	RELSCHEMA_ARG = 0,
+	RELNAME_ARG,
 	RELPAGES_ARG,
 	RELTUPLES_ARG,
 	RELALLVISIBLE_ARG,
@@ -42,7 +46,8 @@ enum relation_stats_argnum
 
 static struct StatsArgInfo relarginfo[] =
 {
-	[RELATION_ARG] = {"relation", REGCLASSOID},
+	[RELSCHEMA_ARG] = {"schemaname", TEXTOID},
+	[RELNAME_ARG] = {"relname", TEXTOID},
 	[RELPAGES_ARG] = {"relpages", INT4OID},
 	[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
 	[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
@@ -59,6 +64,9 @@ static bool
 relation_statistics_update(FunctionCallInfo fcinfo)
 {
 	bool		result = true;
+	char	   *nspname;
+	Oid			nspoid;
+	char	   *relname;
 	Oid			reloid;
 	Relation	crel;
 	BlockNumber relpages = 0;
@@ -76,6 +84,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
+	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+	nspoid = stats_schema_check_privileges(nspname);
+	if (!OidIsValid(nspoid))
+		return false;
+
+	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+	reloid = get_relname_relid(relname, nspoid);
+	if (!OidIsValid(reloid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Relation \"%s\".\"%s\" not found.", nspname, relname)));
+		return false;
+	}
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	stats_lock_check_privileges(reloid);
+
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
 		relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -108,17 +142,6 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 		update_relallfrozen = true;
 	}
 
-	stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
-	reloid = PG_GETARG_OID(RELATION_ARG);
-
-	if (RecoveryInProgress())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("recovery is in progress"),
-				 errhint("Statistics cannot be modified during recovery.")));
-
-	stats_lock_check_privileges(reloid);
-
 	/*
 	 * Take RowExclusiveLock on pg_class, consistent with
 	 * vac_update_relstats().
@@ -187,20 +210,22 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 Datum
 pg_clear_relation_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(newfcinfo, 5);
+	LOCAL_FCINFO(newfcinfo, 6);
 
-	InitFunctionCallInfoData(*newfcinfo, NULL, 5, InvalidOid, NULL, NULL);
+	InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
 
-	newfcinfo->args[0].value = PG_GETARG_OID(0);
+	newfcinfo->args[0].value = PG_GETARG_DATUM(0);
 	newfcinfo->args[0].isnull = PG_ARGISNULL(0);
-	newfcinfo->args[1].value = UInt32GetDatum(0);
-	newfcinfo->args[1].isnull = false;
-	newfcinfo->args[2].value = Float4GetDatum(-1.0);
+	newfcinfo->args[1].value = PG_GETARG_DATUM(1);
+	newfcinfo->args[1].isnull = PG_ARGISNULL(1);
+	newfcinfo->args[2].value = UInt32GetDatum(0);
 	newfcinfo->args[2].isnull = false;
-	newfcinfo->args[3].value = UInt32GetDatum(0);
+	newfcinfo->args[3].value = Float4GetDatum(-1.0);
 	newfcinfo->args[3].isnull = false;
 	newfcinfo->args[4].value = UInt32GetDatum(0);
 	newfcinfo->args[4].isnull = false;
+	newfcinfo->args[5].value = UInt32GetDatum(0);
+	newfcinfo->args[5].isnull = false;
 
 	relation_statistics_update(newfcinfo);
 	PG_RETURN_VOID();
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 9647f5108b3..b444f6871df 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -18,7 +18,9 @@
 
 #include "access/relation.h"
 #include "catalog/index.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "statistics/stat_utils.h"
@@ -213,6 +215,41 @@ stats_lock_check_privileges(Oid reloid)
 	relation_close(table, NoLock);
 }
 
+
+/*
+ * Resolve a schema name into an Oid, ensure that the user has usage privs on
+ * that schema.
+ */
+Oid
+stats_schema_check_privileges(const char *nspname)
+{
+	Oid			nspoid;
+	AclResult	aclresult;
+
+	nspoid = LookupExplicitNamespace(nspname, true);
+
+	if (nspoid == InvalidOid)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_SCHEMA_NAME),
+				 errmsg("schema %s does not exist", nspname)));
+		return InvalidOid;
+	}
+
+	aclresult = object_aclcheck(NamespaceRelationId, nspoid, GetUserId(), ACL_USAGE);
+
+	if (aclresult != ACLCHECK_OK)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for schema %s", nspname)));
+		return InvalidOid;
+	}
+
+	return nspoid;
+}
+
+
 /*
  * Find the argument number for the given argument name, returning -1 if not
  * found.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 428ed2d60fc..239664c459d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10498,7 +10498,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 	PQExpBuffer out;
 	DumpId	   *deps = NULL;
 	int			ndeps = 0;
-	char	   *qualified_name;
 	int			i_attname;
 	int			i_inherited;
 	int			i_null_frac;
@@ -10563,15 +10562,16 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 
 	out = createPQExpBuffer();
 
-	qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo));
-
 	/* restore relation stats */
 	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
 	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'relation', ");
-	appendStringLiteralAH(out, qualified_name, fout);
-	appendPQExpBufferStr(out, "::regclass,\n");
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n");
+	appendPQExpBufferStr(out, "\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n");
 	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
 	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
 	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
@@ -10610,9 +10610,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
 		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'relation', ");
-		appendStringLiteralAH(out, qualified_name, fout);
-		appendPQExpBufferStr(out, "::regclass");
+		appendPQExpBufferStr(out, "\t'schemaname', ");
+		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(out, ",\n\t'relname', ");
+		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10624,7 +10625,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 * their attnames are not necessarily stable across dump/reload.
 		 */
 		if (rsinfo->nindAttNames == 0)
-			appendNamedArgument(out, fout, "attname", "name", attname);
+		{
+			appendPQExpBuffer(out, ",\n\t'attname', ");
+			appendStringLiteralAH(out, attname, fout);
+		}
 		else
 		{
 			bool		found = false;
@@ -10704,7 +10708,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							  .deps = deps,
 							  .nDeps = ndeps));
 
-	free(qualified_name);
 	destroyPQExpBuffer(out);
 	destroyPQExpBuffer(query);
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d281e27aa67..d3e84f44c6c 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4741,14 +4741,16 @@ my %tests = (
 		regexp => qr/^
 			\QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-			'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+			'schemaname',\s'dump_test',\s+
+			'relname',\s'dup_test_post_data_ix',\s+
 			'relpages',\s'\d+'::integer,\s+
 			'reltuples',\s'\d+'::real,\s+
 			'relallvisible',\s'\d+'::integer\s+
 			\);\s+
 			\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-			'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+
+			'schemaname',\s'dump_test',\s+
+			'relname',\s'dup_test_post_data_ix',\s+
 			'attnum',\s'2'::smallint,\s+
 			'inherited',\s'f'::boolean,\s+
 			'null_frac',\s'0'::real,\s+
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index cf381867e40..c68ff9cbf83 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	202503241
+#define CATALOG_VERSION_NO	202503242
 
 #endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0d29ef50ff2..3f7b82e02bb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12453,8 +12453,8 @@
   descr => 'clear statistics on relation',
   proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
   proparallel => 'u', prorettype => 'void',
-  proargtypes => 'regclass',
-  proargnames => '{relation}',
+  proargtypes => 'text text',
+  proargnames => '{schemaname,relname}',
   prosrc => 'pg_clear_relation_stats' },
 { oid => '8461',
   descr => 'restore statistics on attribute',
@@ -12469,8 +12469,8 @@
   descr => 'clear statistics on attribute',
   proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
   proparallel => 'u', prorettype => 'void',
-  proargtypes => 'regclass name bool',
-  proargnames => '{relation,attname,inherited}',
+  proargtypes => 'text text text bool',
+  proargnames => '{schemaname,relname,attname,inherited}',
   prosrc => 'pg_clear_attribute_stats' },
 
 # GiST stratnum implementations
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 0eb4decfcac..ba09b431c11 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -32,6 +32,8 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 
 extern void stats_lock_check_privileges(Oid reloid);
 
+extern Oid	stats_schema_check_privileges(const char *nspname);
+
 extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 											 FunctionCallInfo positional_fcinfo,
 											 struct StatsArgInfo *arginfo);
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 1f46d5e7854..302e77743e3 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -14,7 +14,8 @@ CREATE TABLE stats_import.test(
 ) WITH (autovacuum_enabled = false);
 SELECT
     pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 18::integer,
         'reltuples', 21::real,
         'relallvisible', 24::integer,
@@ -36,7 +37,7 @@ ORDER BY relname;
  test    |       18 |        21 |            24 |           27
 (1 row)
 
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
  pg_clear_relation_stats 
 -------------------------
  
@@ -45,33 +46,54 @@ SELECT pg_clear_relation_stats('stats_import.test'::regclass);
 --
 -- relstats tests
 --
---- error: relation is wrong type
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid,
+        'relname', 'test',
         'relpages', 17::integer);
-WARNING:  argument "relation" has type "oid", expected type "regclass"
-ERROR:  "relation" cannot be NULL
+ERROR:  "schemaname" cannot be NULL
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relpages', 17::integer);
+ERROR:  "relname" cannot be NULL
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 3.6::float,
+        'relname', 'test',
+        'relpages', 17::integer);
+WARNING:  argument "schemaname" has type "double precision", expected type "text"
+ERROR:  "schemaname" cannot be NULL
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relname', 0::oid,
+        'relpages', 17::integer);
+WARNING:  argument "relname" has type "oid", expected type "text"
+ERROR:  "relname" cannot be NULL
 -- error: relation not found
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'nope',
         'relpages', 17::integer);
-ERROR:  could not open relation with OID 0
+WARNING:  Relation "stats_import"."nope" not found.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- error: odd number of variadic arguments cannot be pairs
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible');
 ERROR:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
 -- error: argument name is NULL
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 3 is NULL
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
-        'relation', '0'::oid::regclass,
-        17, '17'::integer);
-ERROR:  name at variadic position 3 has type "integer", expected type "text"
+ERROR:  name at variadic position 5 is NULL
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -84,7 +106,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
 -- regular indexes have special case locking rules
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test_i',
         'relpages', 18::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -132,7 +155,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
 --
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.part_parent_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'part_parent_i',
         'relpages', 2::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -166,7 +190,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
 
 -- ok: set all relstats, with version, no bounds checking
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relpages', '-17'::integer,
         'reltuples', 400::real,
@@ -187,7 +212,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relpages, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '16'::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -204,7 +230,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just reltuples, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'reltuples', '500'::real);
  pg_restore_relation_stats 
 ---------------------------
@@ -221,7 +248,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relallvisible, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible', 5::integer);
  pg_restore_relation_stats 
 ---------------------------
@@ -238,7 +266,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: just relallfrozen
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relallfrozen', 3::integer);
  pg_restore_relation_stats 
@@ -256,7 +285,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- warn: bad relpages type, rest updated
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 'nope'::text,
         'reltuples', 400.0::real,
         'relallvisible', 4::integer,
@@ -277,7 +307,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- unrecognized argument name, rest ok
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '171'::integer,
         'nope', 10::integer);
 WARNING:  unrecognized argument name: "nope"
@@ -295,8 +326,7 @@ WHERE oid = 'stats_import.test'::regclass;
 (1 row)
 
 -- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
-    relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
  pg_clear_relation_stats 
 -------------------------
  
@@ -313,87 +343,123 @@ WHERE oid = 'stats_import.test'::regclass;
 -- invalid relkinds for statistics
 CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testseq'::regclass);
+        'schemaname', 'stats_import',
+        'relname', 'testseq');
 ERROR:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
 ERROR:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
-SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testview'::regclass);
-ERROR:  cannot modify statistics for relation "testview"
-DETAIL:  This operation is not supported for views.
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
 ERROR:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
 --
 -- attribute stats
 --
--- error: object does not exist
+-- error: schemaname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+ERROR:  "schemaname" cannot be NULL
+-- error: schema does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'nope',
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+WARNING:  schema nope does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+ERROR:  "relname" cannot be NULL
+-- error: relname does not exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', '0'::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'nope',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  could not open relation with OID 0
--- error: relation null
+WARNING:  Relation "stats_import"."nope" not found.
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- error: relname null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', NULL::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', NULL,
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relation" cannot be NULL
+ERROR:  "relname" cannot be NULL
 -- error: NULL attname
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', NULL::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  must specify either attname or attnum
 -- error: attname doesn't exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'nope'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'nope',
     'inherited', false::boolean,
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "test" does not exist
+ERROR:  column "nope" of relation "stats_import"."test" does not exist
 -- error: both attname and attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  cannot specify both attname and attnum
 -- error: neither attname nor attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  must specify either attname or attnum
 -- error: attribute is system column
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'xmin'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 ERROR:  cannot modify statistics on system column "xmin"
 -- error: inherited null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
 ERROR:  "inherited" cannot be NULL
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'version', 150000::integer,
     'null_frac', 0.2::real,
@@ -421,7 +487,8 @@ AND attname = 'id';
 -- for any stat-having relation.
 --
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.4::real);
@@ -443,8 +510,9 @@ AND attname = 'id';
 
 -- warn: unrecognized argument name, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.2::real,
     'nope', 0.5::real);
@@ -467,8 +535,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 1, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -492,8 +561,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 2, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_vals', '{1,2,3}'::text
@@ -517,8 +587,9 @@ AND attname = 'id';
 
 -- warn: mcf type mismatch, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.22::real,
     'most_common_vals', '{2,1,3}'::text,
@@ -544,8 +615,9 @@ AND attname = 'id';
 
 -- warn: mcv cast failure, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.23::real,
     'most_common_vals', '{2,four,3}'::text,
@@ -570,8 +642,9 @@ AND attname = 'id';
 
 -- ok: mcv+mcf
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'most_common_vals', '{2,1,3}'::text,
     'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -594,8 +667,9 @@ AND attname = 'id';
 
 -- warn: NULL in histogram array, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.24::real,
     'histogram_bounds', '{1,NULL,3,4}'::text
@@ -619,8 +693,9 @@ AND attname = 'id';
 
 -- ok: histogram_bounds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'histogram_bounds', '{1,2,3,4}'::text
     );
@@ -642,8 +717,9 @@ AND attname = 'id';
 
 -- warn: elem_count_histogram null element, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.25::real,
     'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -667,8 +743,9 @@ AND attname = 'tags';
 
 -- ok: elem_count_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.26::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -691,8 +768,9 @@ AND attname = 'tags';
 
 -- warn: range stats on a scalar type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.27::real,
     'range_empty_frac', 0.5::real,
@@ -718,8 +796,9 @@ AND attname = 'id';
 
 -- warn: range_empty_frac range_length_hist null mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.28::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -743,8 +822,9 @@ AND attname = 'arange';
 
 -- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.29::real,
     'range_empty_frac', 0.5::real
@@ -768,8 +848,9 @@ AND attname = 'arange';
 
 -- ok: range_empty_frac + range_length_hist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_empty_frac', 0.5::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -792,8 +873,9 @@ AND attname = 'arange';
 
 -- warn: range bounds histogram on scalar, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.31::real,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -818,8 +900,9 @@ AND attname = 'id';
 
 -- ok: range_bounds_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
     );
@@ -841,8 +924,9 @@ AND attname = 'arange';
 
 -- warn: cannot set most_common_elems for range type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.32::real,
     'most_common_elems', '{3,1}'::text,
@@ -868,8 +952,9 @@ AND attname = 'arange';
 
 -- warn: scalars can't have mcelem, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.33::real,
     'most_common_elems', '{1,3}'::text,
@@ -895,8 +980,9 @@ AND attname = 'id';
 
 -- warn: mcelem / mcelem mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.34::real,
     'most_common_elems', '{one,two}'::text
@@ -920,8 +1006,9 @@ AND attname = 'tags';
 
 -- warn: mcelem / mcelem null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.35::real,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -945,8 +1032,9 @@ AND attname = 'tags';
 
 -- ok: mcelem
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'most_common_elems', '{one,three}'::text,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -969,8 +1057,9 @@ AND attname = 'tags';
 
 -- warn: scalars can't have elem_count_histogram, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.36::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -1022,8 +1111,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
 FROM pg_catalog.pg_stats AS s
 CROSS JOIN LATERAL
     pg_catalog.pg_restore_attribute_stats(
-        'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
-        'attname', s.attname,
+        'schemaname', 'stats_import',
+        'relname', s.tablename::text || '_clone',
+        'attname', s.attname::text,
         'inherited', s.inherited,
         'version', 150000,
         'null_frac', s.null_frac,
@@ -1200,9 +1290,10 @@ AND attname = 'arange';
 (1 row)
 
 SELECT pg_catalog.pg_clear_attribute_stats(
-    relation => 'stats_import.test'::regclass,
-    attname => 'arange'::name,
-    inherited => false::boolean);
+    schemaname => 'stats_import',
+    relname => 'test',
+    attname => 'arange',
+    inherited => false);
  pg_clear_attribute_stats 
 --------------------------
  
@@ -1219,6 +1310,52 @@ AND attname = 'arange';
      0
 (1 row)
 
+-- temp tables
+CREATE TEMP TABLE stats_temp(i int);
+SELECT pg_restore_relation_stats(
+        'schemaname', 'pg_temp',
+        'relname', 'stats_temp',
+        'relpages', '-19'::integer,
+        'reltuples', 401::real,
+        'relallvisible', 5::integer,
+        'relallfrozen', 3::integer);
+ pg_restore_relation_stats 
+---------------------------
+ t
+(1 row)
+
+SELECT relname, relpages, reltuples, relallvisible, relallfrozen
+FROM pg_class
+WHERE oid = 'pg_temp.stats_temp'::regclass
+ORDER BY relname;
+  relname   | relpages | reltuples | relallvisible | relallfrozen 
+------------+----------+-----------+---------------+--------------
+ stats_temp |      -19 |       401 |             5 |            3
+(1 row)
+
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'pg_temp',
+    'relname', 'stats_temp',
+    'attname', 'i',
+    'inherited', false::boolean,
+    'null_frac', 0.0123::real
+    );
+ pg_restore_attribute_stats 
+----------------------------
+ t
+(1 row)
+
+SELECT tablename, null_frac
+FROM pg_stats
+WHERE schemaname like 'pg_temp%'
+AND tablename = 'stats_temp'
+AND inherited = false
+AND attname = 'i';
+ tablename  | null_frac 
+------------+-----------
+ stats_temp |    0.0123
+(1 row)
+
 DROP SCHEMA stats_import CASCADE;
 NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to type stats_import.complex_type
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 0ec590688c2..35a9a1e3e7a 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -17,7 +17,8 @@ CREATE TABLE stats_import.test(
 
 SELECT
     pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 18::integer,
         'reltuples', 21::real,
         'relallvisible', 24::integer,
@@ -32,37 +33,52 @@ FROM pg_class
 WHERE oid = 'stats_import.test'::regclass
 ORDER BY relname;
 
-SELECT pg_clear_relation_stats('stats_import.test'::regclass);
+SELECT pg_clear_relation_stats('stats_import', 'test');
 
 --
 -- relstats tests
 --
 
---- error: relation is wrong type
+-- error: schemaname missing
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid,
+        'relname', 'test',
+        'relpages', 17::integer);
+
+-- error: relname missing
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relpages', 17::integer);
+
+--- error: schemaname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 3.6::float,
+        'relname', 'test',
+        'relpages', 17::integer);
+
+--- error: relname is wrong type
+SELECT pg_catalog.pg_restore_relation_stats(
+        'schemaname', 'stats_import',
+        'relname', 0::oid,
         'relpages', 17::integer);
 
 -- error: relation not found
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 0::oid::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'nope',
         'relpages', 17::integer);
 
 -- error: odd number of variadic arguments cannot be pairs
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible');
 
 -- error: argument name is NULL
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         NULL, '17'::integer);
 
--- error: argument name is not a text type
-SELECT pg_restore_relation_stats(
-        'relation', '0'::oid::regclass,
-        17, '17'::integer);
-
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -71,7 +87,8 @@ WHERE oid = 'stats_import.test_i'::regclass;
 -- regular indexes have special case locking rules
 BEGIN;
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.test_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test_i',
         'relpages', 18::integer);
 
 SELECT mode FROM pg_locks
@@ -108,7 +125,8 @@ WHERE oid = 'stats_import.part_parent'::regclass;
 BEGIN;
 
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.part_parent_i'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'part_parent_i',
         'relpages', 2::integer);
 
 SELECT mode FROM pg_locks
@@ -127,7 +145,8 @@ WHERE oid = 'stats_import.part_parent_i'::regclass;
 
 -- ok: set all relstats, with version, no bounds checking
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relpages', '-17'::integer,
         'reltuples', 400::real,
@@ -140,7 +159,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relpages, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '16'::integer);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -149,7 +169,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just reltuples, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'reltuples', '500'::real);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -158,7 +179,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: set just relallvisible, rest stay same
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relallvisible', 5::integer);
 
 SELECT relpages, reltuples, relallvisible, relallfrozen
@@ -167,7 +189,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: just relallfrozen
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'version', 150000::integer,
         'relallfrozen', 3::integer);
 
@@ -177,7 +200,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- warn: bad relpages type, rest updated
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', 'nope'::text,
         'reltuples', 400.0::real,
         'relallvisible', 4::integer,
@@ -189,7 +213,8 @@ WHERE oid = 'stats_import.test'::regclass;
 
 -- unrecognized argument name, rest ok
 SELECT pg_restore_relation_stats(
-        'relation', 'stats_import.test'::regclass,
+        'schemaname', 'stats_import',
+        'relname', 'test',
         'relpages', '171'::integer,
         'nope', 10::integer);
 
@@ -198,8 +223,7 @@ FROM pg_class
 WHERE oid = 'stats_import.test'::regclass;
 
 -- ok: clear stats
-SELECT pg_catalog.pg_clear_relation_stats(
-    relation => 'stats_import.test'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
 
 SELECT relpages, reltuples, relallvisible
 FROM pg_class
@@ -209,48 +233,70 @@ WHERE oid = 'stats_import.test'::regclass;
 CREATE SEQUENCE stats_import.testseq;
 
 SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testseq'::regclass);
+        'schemaname', 'stats_import',
+        'relname', 'testseq');
 
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testseq'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
 
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 
-SELECT pg_catalog.pg_restore_relation_stats(
-        'relation', 'stats_import.testview'::regclass);
-
-SELECT pg_catalog.pg_clear_relation_stats(
-        'stats_import.testview'::regclass);
+SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
 
 --
 -- attribute stats
 --
 
--- error: object does not exist
+-- error: schemaname missing
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: schema does not exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', '0'::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'nope',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relation null
+-- error: relname missing
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', NULL::oid::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'nope',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'null_frac', 0.1::real);
+
+-- error: relname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', NULL,
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: NULL attname
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', NULL::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: attname doesn't exist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'nope'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'nope',
     'inherited', false::boolean,
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
@@ -258,36 +304,41 @@ SELECT pg_catalog.pg_restore_attribute_stats(
 
 -- error: both attname and attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: neither attname nor attnum
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: attribute is system column
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'xmin'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
 -- error: inherited null
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
 
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'version', 150000::integer,
     'null_frac', 0.2::real,
@@ -307,7 +358,8 @@ AND attname = 'id';
 -- for any stat-having relation.
 --
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
+    'schemaname', 'stats_import',
+    'relname', 'test',
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.4::real);
@@ -321,8 +373,9 @@ AND attname = 'id';
 
 -- warn: unrecognized argument name, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.2::real,
     'nope', 0.5::real);
@@ -336,8 +389,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 1, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_freqs', '{0.1,0.2,0.3}'::real[]
@@ -352,8 +406,9 @@ AND attname = 'id';
 
 -- warn: mcv / mcf null mismatch part 2, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.21::real,
     'most_common_vals', '{1,2,3}'::text
@@ -368,8 +423,9 @@ AND attname = 'id';
 
 -- warn: mcf type mismatch, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.22::real,
     'most_common_vals', '{2,1,3}'::text,
@@ -385,8 +441,9 @@ AND attname = 'id';
 
 -- warn: mcv cast failure, mcv-pair fails, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.23::real,
     'most_common_vals', '{2,four,3}'::text,
@@ -402,8 +459,9 @@ AND attname = 'id';
 
 -- ok: mcv+mcf
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'most_common_vals', '{2,1,3}'::text,
     'most_common_freqs', '{0.3,0.25,0.05}'::real[]
@@ -418,8 +476,9 @@ AND attname = 'id';
 
 -- warn: NULL in histogram array, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.24::real,
     'histogram_bounds', '{1,NULL,3,4}'::text
@@ -434,8 +493,9 @@ AND attname = 'id';
 
 -- ok: histogram_bounds
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'histogram_bounds', '{1,2,3,4}'::text
     );
@@ -449,8 +509,9 @@ AND attname = 'id';
 
 -- warn: elem_count_histogram null element, rest get set
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.25::real,
     'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
@@ -465,8 +526,9 @@ AND attname = 'tags';
 
 -- ok: elem_count_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.26::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -481,8 +543,9 @@ AND attname = 'tags';
 
 -- warn: range stats on a scalar type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.27::real,
     'range_empty_frac', 0.5::real,
@@ -498,8 +561,9 @@ AND attname = 'id';
 
 -- warn: range_empty_frac range_length_hist null mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.28::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -514,8 +578,9 @@ AND attname = 'arange';
 
 -- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.29::real,
     'range_empty_frac', 0.5::real
@@ -530,8 +595,9 @@ AND attname = 'arange';
 
 -- ok: range_empty_frac + range_length_hist
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_empty_frac', 0.5::real,
     'range_length_histogram', '{399,499,Infinity}'::text
@@ -546,8 +612,9 @@ AND attname = 'arange';
 
 -- warn: range bounds histogram on scalar, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.31::real,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
@@ -562,8 +629,9 @@ AND attname = 'id';
 
 -- ok: range_bounds_histogram
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
     );
@@ -577,8 +645,9 @@ AND attname = 'arange';
 
 -- warn: cannot set most_common_elems for range type, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'arange'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
     'inherited', false::boolean,
     'null_frac', 0.32::real,
     'most_common_elems', '{3,1}'::text,
@@ -594,8 +663,9 @@ AND attname = 'arange';
 
 -- warn: scalars can't have mcelem, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.33::real,
     'most_common_elems', '{1,3}'::text,
@@ -611,8 +681,9 @@ AND attname = 'id';
 
 -- warn: mcelem / mcelem mismatch, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.34::real,
     'most_common_elems', '{one,two}'::text
@@ -627,8 +698,9 @@ AND attname = 'tags';
 
 -- warn: mcelem / mcelem null mismatch part 2, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'null_frac', 0.35::real,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
@@ -643,8 +715,9 @@ AND attname = 'tags';
 
 -- ok: mcelem
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'tags'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'tags',
     'inherited', false::boolean,
     'most_common_elems', '{one,three}'::text,
     'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
@@ -659,8 +732,9 @@ AND attname = 'tags';
 
 -- warn: scalars can't have elem_count_histogram, rest ok
 SELECT pg_catalog.pg_restore_attribute_stats(
-    'relation', 'stats_import.test'::regclass,
-    'attname', 'id'::name,
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.36::real,
     'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
@@ -707,8 +781,9 @@ SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
 FROM pg_catalog.pg_stats AS s
 CROSS JOIN LATERAL
     pg_catalog.pg_restore_attribute_stats(
-        'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
-        'attname', s.attname,
+        'schemaname', 'stats_import',
+        'relname', s.tablename::text || '_clone',
+        'attname', s.attname::text,
         'inherited', s.inherited,
         'version', 150000,
         'null_frac', s.null_frac,
@@ -853,9 +928,10 @@ AND inherited = false
 AND attname = 'arange';
 
 SELECT pg_catalog.pg_clear_attribute_stats(
-    relation => 'stats_import.test'::regclass,
-    attname => 'arange'::name,
-    inherited => false::boolean);
+    schemaname => 'stats_import',
+    relname => 'test',
+    attname => 'arange',
+    inherited => false);
 
 SELECT COUNT(*)
 FROM pg_stats
@@ -864,4 +940,34 @@ AND tablename = 'test'
 AND inherited = false
 AND attname = 'arange';
 
+-- temp tables
+CREATE TEMP TABLE stats_temp(i int);
+SELECT pg_restore_relation_stats(
+        'schemaname', 'pg_temp',
+        'relname', 'stats_temp',
+        'relpages', '-19'::integer,
+        'reltuples', 401::real,
+        'relallvisible', 5::integer,
+        'relallfrozen', 3::integer);
+
+SELECT relname, relpages, reltuples, relallvisible, relallfrozen
+FROM pg_class
+WHERE oid = 'pg_temp.stats_temp'::regclass
+ORDER BY relname;
+
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'pg_temp',
+    'relname', 'stats_temp',
+    'attname', 'i',
+    'inherited', false::boolean,
+    'null_frac', 0.0123::real
+    );
+
+SELECT tablename, null_frac
+FROM pg_stats
+WHERE schemaname like 'pg_temp%'
+AND tablename = 'stats_temp'
+AND inherited = false
+AND attname = 'i';
+
 DROP SCHEMA stats_import CASCADE;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-25 14:53                       ` Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-25 14:53 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> * Changed to use LookupExplicitNamespace()
>

Seems good.


> * Added test for temp tables
>

+1


> * Doc fixes


So this patch swings the pendulum a bit back towards accepting some things
as errors. That's understandable, as we're never going to have a situation
where we can guarantee that the restore functions never generate an error,
so the best we can do is to draw the error-versus-warning line at a place
that:

* doesn't mess up flawed restores that we would otherwise expect to
complete at least partially
* is easy for us to understand
* is easy for us to explain
* we can live with for the next couple of decades

I don't know where that line should be drawn, so if people are happy with
Jeff's demarcation, then less roll with it.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-25 18:42                         ` Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-25 18:42 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Tue, 2025-03-25 at 10:53 -0400, Corey Huinker wrote:
> 
> So this patch swings the pendulum a bit back towards accepting some
> things as errors.

Not exactly. I see patch 0001 as a change to the function signatures
from regclass to schemaname/relname, both for usability as well as
control over ERROR vs WARNING.

There's agreement to do so, so I went ahead and committed that part.

> the best we can do is to draw the error-versus-warning line at a
> place that:
> 
> * doesn't mess up flawed restores that we would otherwise expect to
> complete at least partially
> * is easy for us to understand
> * is easy for us to explain
> * we can live with for the next couple of decades

The original reason we wanted to issue warnings was to allow ourselves
a chance to change the meaning of parameters, add new parameters, or
even remove parameters without causing restore failures. If there are
any ERRORs that might limit our flexibility I think we should downgrade
those to WARNINGs.

Also, out of a sense of paranoia, it might be good to downgrade some
other ERRORs to WARNINGs, like in 0002. I don't think it's quite as
important as you seem to think, however. It doesn't make a lot of
difference unless the user is running restore with --single-transaction
or --exit-on-error, in which case they probably don't want the restore
to continue if something unexpected happens. I'm fine having the
discussion, though, or we can wait until beta to see what kinds of
problems people encounter.

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-25 19:59                           ` Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-25 19:59 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> The original reason we wanted to issue warnings was to allow ourselves
> a chance to change the meaning of parameters, add new parameters, or
> even remove parameters without causing restore failures. If there are
> any ERRORs that might limit our flexibility I think we should downgrade
> those to WARNINGs.
>

+1


> Also, out of a sense of paranoia, it might be good to downgrade some
> other ERRORs to WARNINGs, like in 0002. I don't think it's quite as
> important as you seem to think, however. It doesn't make a lot of
> difference unless the user is running restore with --single-transaction
> or --exit-on-error, in which case they probably don't want the restore
> to continue if something unexpected happens. I'm fine having the
> discussion, though, or we can wait until beta to see what kinds of
> problems people encounter.
>

At this point, I feel I've demonstrated the limit of what can be made into
WARNINGs, giving us a range of options for now and into the beta. I'll
rebase and move the 0002 patch to be in last position so as to tee up
0003-0004 for consideration.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-26 01:41                             ` Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-26 01:41 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> At this point, I feel I've demonstrated the limit of what can be made into
> WARNINGs, giving us a range of options for now and into the beta. I'll
> rebase and move the 0002 patch to be in last position so as to tee up
> 0003-0004 for consideration.
>

And here's the rebase (after bde2fb797aaebcbe06bf60f330ba5a068f17dda7).

The order of the patches is different, but the purpose of each is the same
as before.


Attachments:

  [text/x-patch] v10-0002-Batching-getAttributeStats.patch (21.5K, ../../CADkLM=ftC94muGKH+z1irdBUXO2+tmBMLdqDAAxcct=H+LE1Eg@mail.gmail.com/3-v10-0002-Batching-getAttributeStats.patch)
  download | inline diff:
From 1f9b2578f55fa1233121bcf5949a6f69d6cf8cee Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v10 2/4] Batching getAttributeStats().

The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.

The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
 src/bin/pg_dump/pg_dump.c | 554 ++++++++++++++++++++++++++------------
 1 file changed, 383 insertions(+), 171 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 224dc8c9330..e3f2dac33ec 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
 	zeroAsNone = 4,
 } OidOptions;
 
+typedef enum StatsBufferState
+{
+	STATSBUF_UNINITIALIZED = 0,
+	STATSBUF_ACTIVE,
+	STATSBUF_EXHAUSTED
+}			StatsBufferState;
+
+typedef struct
+{
+	PGresult   *res;			/* results from most recent
+								 * getAttributeStats() */
+	int			idx;			/* first un-consumed row of results */
+	TocEntry   *te;				/* next TOC entry to search for statsitics
+								 * data */
+
+	StatsBufferState state;		/* current state of the buffer */
+}			AttributeStatsBuffer;
+
+
 /* global decls */
 static bool dosync = true;		/* Issue fsync() to make dump durable on disk. */
 
@@ -209,6 +228,18 @@ static int	nbinaryUpgradeClassOids = 0;
 static SequenceItem *sequences = NULL;
 static int	nsequences = 0;
 
+static AttributeStatsBuffer attrstats =
+{
+	NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
 /*
  * The default number of rows per INSERT when
  * --inserts is specified without --rows-per-insert
@@ -222,6 +253,8 @@ static int	nsequences = 0;
  */
 #define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
 
+
+
 /*
  * Macro for producing quoted, schema-qualified name of a dumpable object.
  */
@@ -399,6 +432,9 @@ static void setupDumpWorker(Archive *AH);
 static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
 static bool forcePartitionRootLoad(const TableInfo *tbinfo);
 static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+								const char *argname, const char *argtype,
+								const char *argval);
 
 
 int
@@ -10520,7 +10556,286 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
 }
 
 /*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData schemas;
+	PQExpBufferData relations;
+	int			numoids = 0;
+
+	Assert(AH != NULL);
+
+	/* free last result set, if any */
+	if (attrstats.state == STATSBUF_ACTIVE)
+		PQclear(attrstats.res);
+
+	/* If we have looped around to the start of the TOC, restart */
+	if (attrstats.te == AH->toc)
+		attrstats.te = AH->toc->next;
+
+	initPQExpBuffer(&schemas);
+	initPQExpBuffer(&relations);
+
+	/*
+	 * Walk ahead looking for relstats entries that are active in this
+	 * section, adding the names to the schemas and relations lists.
+	 */
+	while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+	{
+		if (attrstats.te->reqs != 0 &&
+			strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+		{
+			RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+			Assert(rsinfo != NULL);
+
+			if (numoids > 0)
+			{
+				appendPQExpBufferStr(&schemas, ",");
+				appendPQExpBufferStr(&relations, ",");
+			}
+			appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+			numoids++;
+		}
+
+		attrstats.te = attrstats.te->next;
+	}
+
+	if (numoids > 0)
+	{
+		PQExpBufferData query;
+
+		initPQExpBuffer(&query);
+		appendPQExpBuffer(&query,
+						  "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+						  schemas.data, relations.data);
+		attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+		attrstats.idx = 0;
+	}
+	else
+	{
+		attrstats.state = STATSBUF_EXHAUSTED;
+		attrstats.res = NULL;
+		attrstats.idx = -1;
+	}
+
+	termPQExpBuffer(&schemas);
+	termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData query;
+
+	Assert(AH != NULL);
+	initPQExpBuffer(&query);
+
+	appendPQExpBufferStr(&query,
+						 "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+						 "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+						 "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+						 "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+						 "s.most_common_elems, s.most_common_elem_freqs, "
+						 "s.elem_count_histogram, ");
+
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(&query,
+							 "s.range_length_histogram, "
+							 "s.range_empty_frac, "
+							 "s.range_bounds_histogram ");
+	else
+		appendPQExpBufferStr(&query,
+							 "NULL AS range_length_histogram, "
+							 "NULL AS range_empty_frac, "
+							 " NULL AS range_bounds_histogram ");
+
+	/*
+	 * The results must be in the order of relations supplied in the
+	 * parameters to ensure that they are in sync with a walk of the TOC.
+	 *
+	 * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+	 * is a way to lead the query into using the index
+	 * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+	 * expensive full scan of pg_stats.
+	 *
+	 * We may need to adjust this query for versions that are not so easily
+	 * led.
+	 */
+	appendPQExpBufferStr(&query,
+						 "FROM pg_catalog.pg_stats AS s "
+						 "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+						 "ON s.schemaname = u.schemaname "
+						 "AND s.tablename = u.tablename "
+						 "WHERE s.tablename = ANY($2) "
+						 "ORDER BY u.ord, s.attname, s.inherited");
+
+	ExecuteSqlStatement(fout, query.data);
+
+	termPQExpBuffer(&query);
+
+	attrstats.te = AH->toc->next;
+
+	fetchNextAttributeStats(fout);
+
+	attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+					 const RelStatsInfo *rsinfo)
+{
+	PGresult   *res = attrstats.res;
+	int			tup_num = attrstats.idx;
+
+	const char *attname;
+
+	static bool indexes_set = false;
+	static int	i_attname,
+				i_inherited,
+				i_null_frac,
+				i_avg_width,
+				i_n_distinct,
+				i_most_common_vals,
+				i_most_common_freqs,
+				i_histogram_bounds,
+				i_correlation,
+				i_most_common_elems,
+				i_most_common_elem_freqs,
+				i_elem_count_histogram,
+				i_range_length_histogram,
+				i_range_empty_frac,
+				i_range_bounds_histogram;
+
+	if (!indexes_set)
+	{
+		/*
+		 * It's a prepared statement, so the indexes will be the same for all
+		 * result sets, so we only need to set them once.
+		 */
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		indexes_set = true;
+	}
+
+	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+					  fout->remoteVersion);
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+	if (PQgetisnull(res, tup_num, i_attname))
+		pg_fatal("attname cannot be NULL");
+	attname = PQgetvalue(res, tup_num, i_attname);
+
+	/*
+	 * Indexes look up attname in indAttNames to derive attnum, all others use
+	 * attname directly.  We must specify attnum for indexes, since their
+	 * attnames are not necessarily stable across dump/reload.
+	 */
+	if (rsinfo->nindAttNames == 0)
+	{
+		appendPQExpBuffer(out, ",\n\t'attname', ");
+		appendStringLiteralAH(out, attname, fout);
+	}
+	else
+	{
+		bool		found = false;
+
+		for (int i = 0; i < rsinfo->nindAttNames; i++)
+			if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+			{
+				appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+								  i + 1);
+				found = true;
+				break;
+			}
+
+		if (!found)
+			pg_fatal("could not find index attname \"%s\"", attname);
+	}
+
+	if (!PQgetisnull(res, tup_num, i_inherited))
+		appendNamedArgument(out, fout, "inherited", "boolean",
+							PQgetvalue(res, tup_num, i_inherited));
+	if (!PQgetisnull(res, tup_num, i_null_frac))
+		appendNamedArgument(out, fout, "null_frac", "real",
+							PQgetvalue(res, tup_num, i_null_frac));
+	if (!PQgetisnull(res, tup_num, i_avg_width))
+		appendNamedArgument(out, fout, "avg_width", "integer",
+							PQgetvalue(res, tup_num, i_avg_width));
+	if (!PQgetisnull(res, tup_num, i_n_distinct))
+		appendNamedArgument(out, fout, "n_distinct", "real",
+							PQgetvalue(res, tup_num, i_n_distinct));
+	if (!PQgetisnull(res, tup_num, i_most_common_vals))
+		appendNamedArgument(out, fout, "most_common_vals", "text",
+							PQgetvalue(res, tup_num, i_most_common_vals));
+	if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+		appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_freqs));
+	if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+		appendNamedArgument(out, fout, "histogram_bounds", "text",
+							PQgetvalue(res, tup_num, i_histogram_bounds));
+	if (!PQgetisnull(res, tup_num, i_correlation))
+		appendNamedArgument(out, fout, "correlation", "real",
+							PQgetvalue(res, tup_num, i_correlation));
+	if (!PQgetisnull(res, tup_num, i_most_common_elems))
+		appendNamedArgument(out, fout, "most_common_elems", "text",
+							PQgetvalue(res, tup_num, i_most_common_elems));
+	if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+		appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+	if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+		appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+							PQgetvalue(res, tup_num, i_elem_count_histogram));
+	if (fout->remoteVersion >= 170000)
+	{
+		if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+			appendNamedArgument(out, fout, "range_length_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_length_histogram));
+		if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+			appendNamedArgument(out, fout, "range_empty_frac", "real",
+								PQgetvalue(res, tup_num, i_range_empty_frac));
+		if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+			appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_bounds_histogram));
+	}
+	appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
  *
  * Generate the SQL statements needed to restore a relation's statistics.
  */
@@ -10528,64 +10843,21 @@ static char *
 printRelationStats(Archive *fout, const void *userArg)
 {
 	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
-	const DumpableObject *dobj = &rsinfo->dobj;
+	const DumpableObject *dobj;
+	const char *relschema;
+	const char *relname;
+
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
 
-	PQExpBufferData query;
 	PQExpBufferData out;
 
-	PGresult   *res;
-
-	static bool first_query = true;
-	static int	i_attname;
-	static int	i_inherited;
-	static int	i_null_frac;
-	static int	i_avg_width;
-	static int	i_n_distinct;
-	static int	i_most_common_vals;
-	static int	i_most_common_freqs;
-	static int	i_histogram_bounds;
-	static int	i_correlation;
-	static int	i_most_common_elems;
-	static int	i_most_common_elem_freqs;
-	static int	i_elem_count_histogram;
-	static int	i_range_length_histogram;
-	static int	i_range_empty_frac;
-	static int	i_range_bounds_histogram;
-
-	initPQExpBuffer(&query);
-
-	if (first_query)
-	{
-		appendPQExpBufferStr(&query,
-							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
-							 "SELECT s.attname, s.inherited, "
-							 "s.null_frac, s.avg_width, s.n_distinct, "
-							 "s.most_common_vals, s.most_common_freqs, "
-							 "s.histogram_bounds, s.correlation, "
-							 "s.most_common_elems, s.most_common_elem_freqs, "
-							 "s.elem_count_histogram, ");
-
-		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(&query,
-								 "s.range_length_histogram, "
-								 "s.range_empty_frac, "
-								 "s.range_bounds_histogram ");
-		else
-			appendPQExpBufferStr(&query,
-								 "NULL AS range_length_histogram,"
-								 "NULL AS range_empty_frac,"
-								 "NULL AS range_bounds_histogram ");
-
-		appendPQExpBufferStr(&query,
-							 "FROM pg_catalog.pg_stats s "
-							 "WHERE s.schemaname = $1 "
-							 "AND s.tablename = $2 "
-							 "ORDER BY s.attname, s.inherited");
-
-		ExecuteSqlStatement(fout, query.data);
-
-		resetPQExpBuffer(&query);
-	}
+	Assert(rsinfo != NULL);
+	dobj = &rsinfo->dobj;
+	Assert(dobj != NULL);
+	relschema = dobj->namespace->dobj.name;
+	Assert(relschema != NULL);
+	relname = dobj->name;
+	Assert(relname != NULL);
 
 	initPQExpBuffer(&out);
 
@@ -10604,132 +10876,72 @@ printRelationStats(Archive *fout, const void *userArg)
 	appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
 					  rsinfo->relallvisible);
 
-	/* fetch attribute stats */
-	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(&query, ", ");
-	appendStringLiteralAH(&query, dobj->name, fout);
-	appendPQExpBufferStr(&query, ")");
+	AH->txnCount++;
 
-	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+	if (attrstats.state == STATSBUF_UNINITIALIZED)
+		initAttributeStats(fout);
 
-	if (first_query)
+	/*
+	 * Because the query returns rows in the same order as the relations
+	 * requested, and because every relation gets at least one row in the
+	 * result set, the first row for this relation must correspond either to
+	 * the current row of this result set (if one exists) or the first row of
+	 * the next result set (if this one is already consumed).
+	 */
+	if (attrstats.state != STATSBUF_ACTIVE)
+		pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+				 rsinfo->dobj.namespace->dobj.name,
+				 rsinfo->dobj.name);
+
+	/*
+	 * If the current result set has been fully consumed, then the row(s) we
+	 * need (if any) would be found in the next one. This will update
+	 * attrstats.res and attrstats.idx.
+	 */
+	if (PQntuples(attrstats.res) <= attrstats.idx)
+		fetchNextAttributeStats(fout);
+
+	while (true)
 	{
-		i_attname = PQfnumber(res, "attname");
-		i_inherited = PQfnumber(res, "inherited");
-		i_null_frac = PQfnumber(res, "null_frac");
-		i_avg_width = PQfnumber(res, "avg_width");
-		i_n_distinct = PQfnumber(res, "n_distinct");
-		i_most_common_vals = PQfnumber(res, "most_common_vals");
-		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-		i_correlation = PQfnumber(res, "correlation");
-		i_most_common_elems = PQfnumber(res, "most_common_elems");
-		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
-		first_query = false;
-	}
-
-	/* restore attribute stats */
-	for (int rownum = 0; rownum < PQntuples(res); rownum++)
-	{
-		const char *attname;
-
-		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
-						  fout->remoteVersion);
-		appendPQExpBufferStr(&out, "\t'schemaname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(&out, ",\n\t'relname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
-		if (PQgetisnull(res, rownum, i_attname))
-			pg_fatal("attname cannot be NULL");
-		attname = PQgetvalue(res, rownum, i_attname);
+		int			i_schemaname;
+		int			i_tablename;
+		char	   *schemaname;
+		char	   *tablename;	/* misnomer, following pg_stats naming */
 
 		/*
-		 * Indexes look up attname in indAttNames to derive attnum, all others
-		 * use attname directly.  We must specify attnum for indexes, since
-		 * their attnames are not necessarily stable across dump/reload.
+		 * If we hit the end of the result set, then there are no more records
+		 * for this relation, so we should stop, but first get the next result
+		 * set for the next batch of relations.
 		 */
-		if (rsinfo->nindAttNames == 0)
+		if (PQntuples(attrstats.res) <= attrstats.idx)
 		{
-			appendPQExpBuffer(&out, ",\n\t'attname', ");
-			appendStringLiteralAH(&out, attname, fout);
-		}
-		else
-		{
-			bool		found = false;
-
-			for (int i = 0; i < rsinfo->nindAttNames; i++)
-			{
-				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
-				{
-					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
-									  i + 1);
-					found = true;
-					break;
-				}
-			}
-
-			if (!found)
-				pg_fatal("could not find index attname \"%s\"", attname);
+			fetchNextAttributeStats(fout);
+			break;
 		}
 
-		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(&out, fout, "inherited", "boolean",
-								PQgetvalue(res, rownum, i_inherited));
-		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(&out, fout, "null_frac", "real",
-								PQgetvalue(res, rownum, i_null_frac));
-		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(&out, fout, "avg_width", "integer",
-								PQgetvalue(res, rownum, i_avg_width));
-		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(&out, fout, "n_distinct", "real",
-								PQgetvalue(res, rownum, i_n_distinct));
-		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(&out, fout, "most_common_vals", "text",
-								PQgetvalue(res, rownum, i_most_common_vals));
-		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_freqs));
-		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(&out, fout, "histogram_bounds", "text",
-								PQgetvalue(res, rownum, i_histogram_bounds));
-		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(&out, fout, "correlation", "real",
-								PQgetvalue(res, rownum, i_correlation));
-		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(&out, fout, "most_common_elems", "text",
-								PQgetvalue(res, rownum, i_most_common_elems));
-		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_elem_freqs));
-		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
-								PQgetvalue(res, rownum, i_elem_count_histogram));
-		if (fout->remoteVersion >= 170000)
-		{
-			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(&out, fout, "range_length_histogram", "text",
-									PQgetvalue(res, rownum, i_range_length_histogram));
-			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(&out, fout, "range_empty_frac", "real",
-									PQgetvalue(res, rownum, i_range_empty_frac));
-			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
-									PQgetvalue(res, rownum, i_range_bounds_histogram));
-		}
-		appendPQExpBufferStr(&out, "\n);\n");
+		i_schemaname = PQfnumber(attrstats.res, "schemaname");
+		Assert(i_schemaname >= 0);
+		i_tablename = PQfnumber(attrstats.res, "tablename");
+		Assert(i_tablename >= 0);
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+			pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+			pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+		schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+		tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+		/* stop if current stat row isn't for this relation */
+		if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+			break;
+
+		appendAttributeStats(fout, &out, rsinfo);
+		AH->txnCount++;
+		attrstats.idx++;
 	}
 
-	PQclear(res);
-
-	termPQExpBuffer(&query);
 	return out.data;
 }
 
-- 
2.49.0



  [text/x-patch] v10-0004-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch (30.4K, ../../CADkLM=ftC94muGKH+z1irdBUXO2+tmBMLdqDAAxcct=H+LE1Eg@mail.gmail.com/4-v10-0004-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch)
  download | inline diff:
From 651e70ae705d5a4f081509e66a743422d2e86ae4 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v10 4/4] Downgrade many pg_restore_*_stats errors to warnings.

We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
 src/include/statistics/stat_utils.h        |   4 +-
 src/backend/statistics/attribute_stats.c   | 120 ++++++++++----
 src/backend/statistics/relation_stats.c    |  12 +-
 src/backend/statistics/stat_utils.c        |  65 ++++++--
 src/test/regress/expected/stats_import.out | 184 ++++++++++++++++-----
 src/test/regress/sql/stats_import.sql      |  36 ++--
 6 files changed, 309 insertions(+), 112 deletions(-)

diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 512eb776e0e..809c8263a41 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
 	Oid			argtype;
 };
 
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
 									 struct StatsArgInfo *arginfo,
 									 int argnum);
 extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 								 struct StatsArgInfo *arginfo,
 								 int argnum1, int argnum2);
 
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
 
 extern Oid	stats_lookup_relid(const char *nspname, const char *relname);
 
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f5eb17ba42d..b7ba1622391 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
 
 static bool attribute_statistics_update(FunctionCallInfo fcinfo);
 static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
 							   Oid *atttypid, int32 *atttypmod,
 							   char *atttyptype, Oid *atttypcoll,
 							   Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
  * stored as an anyarray, and the representation of the array needs to store
  * the correct element type, which must be derived from the attribute.
  *
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
  */
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -148,8 +150,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	HeapTuple	statup;
 
 	Oid			atttypid = InvalidOid;
-	int32		atttypmod;
-	char		atttyptype;
+	int32		atttypmod = -1;
+	char		atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
 	Oid			atttypcoll = InvalidOid;
 	Oid			eq_opr = InvalidOid;
 	Oid			lt_opr = InvalidOid;
@@ -176,38 +178,52 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+		return false;
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		return false;
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		return false;
+	}
 
 	/* lock before looking up attribute */
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
 		if (!PG_ARGISNULL(ATTNUM_ARG))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
+			return false;
+		}
 		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" of relation \"%s\" does not exist",
 							attname, relname)));
+			return false;
+		}
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -216,27 +232,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		/* annoyingly, get_attname doesn't check attisdropped */
 		if (attname == NULL ||
 			!SearchSysCacheExistsAttName(reloid, attname))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column %d of relation \"%s\" does not exist",
 							attnum, relname)));
+			return false;
+		}
 	}
 	else
 	{
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("must specify either attname or attnum")));
-		attname = NULL;			/* keep compiler quiet */
-		attnum = 0;
+		return false;
 	}
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
+		return false;
+	}
 
-	stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+		return false;
 	inherited = PG_GETARG_BOOL(INHERITED_ARG);
 
 	/*
@@ -285,10 +307,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* derive information from attribute */
-	get_attr_stat_type(reloid, attnum,
-					   &atttypid, &atttypmod,
-					   &atttyptype, &atttypcoll,
-					   &eq_opr, &lt_opr);
+	if (!get_attr_stat_type(reloid, attnum,
+							&atttypid, &atttypmod,
+							&atttyptype, &atttypcoll,
+							&eq_opr, &lt_opr))
+		result = false;
 
 	/* if needed, derive element type */
 	if (do_mcelem || do_dechist)
@@ -568,7 +591,7 @@ get_attr_expr(Relation rel, int attnum)
 /*
  * Derive type information from the attribute.
  */
-static void
+static bool
 get_attr_stat_type(Oid reloid, AttrNumber attnum,
 				   Oid *atttypid, int32 *atttypmod,
 				   char *atttyptype, Oid *atttypcoll,
@@ -585,18 +608,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 
 	/* Attribute not found */
 	if (!HeapTupleIsValid(atup))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	attr = (Form_pg_attribute) GETSTRUCT(atup);
 
 	if (attr->attisdropped)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	expr = get_attr_expr(rel, attr->attnum);
 
@@ -645,6 +676,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 		*atttypcoll = DEFAULT_COLLATION_OID;
 
 	relation_close(rel, NoLock);
+	return true;
 }
 
 /*
@@ -770,6 +802,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
 	if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
 		slotidx = first_empty;
 
+	/*
+	 * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+	 * statistic kinds, so this can safely remain an ERROR for now.
+	 */
 	if (slotidx >= STATISTIC_NUM_SLOTS)
 		ereport(ERROR,
 				(errmsg("maximum number of statistics slots exceeded: %d",
@@ -915,38 +951,54 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+		PG_RETURN_VOID();
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		PG_RETURN_VOID();
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		PG_RETURN_VOID();
+	}
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		PG_RETURN_VOID();
 
 	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
 	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
 						attname)));
+		PG_RETURN_VOID();
+	}
 
 	if (attnum == InvalidAttrNumber)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
 						attname, get_rel_name(reloid))));
+		PG_RETURN_VOID();
+	}
 
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index cd3a75b621a..7c47af15c9f 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -83,13 +83,18 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
-	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+		return false;
+
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		return false;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -97,7 +102,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index a9a3224efe6..d587e875457 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -33,16 +33,20 @@
 /*
  * Ensure that a given argument is not null.
  */
-void
+bool
 stats_check_required_arg(FunctionCallInfo fcinfo,
 						 struct StatsArgInfo *arginfo,
 						 int argnum)
 {
 	if (PG_ARGISNULL(argnum))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("\"%s\" cannot be NULL",
 						arginfo[argnum].argname)));
+		return false;
+	}
+	return true;
 }
 
 /*
@@ -127,13 +131,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
  *   - the role owns the current database and the relation is not shared
  *   - the role has the MAINTAIN privilege on the relation
  */
-void
+bool
 stats_lock_check_privileges(Oid reloid)
 {
 	Relation	table;
 	Oid			table_oid = reloid;
 	Oid			index_oid = InvalidOid;
 	LOCKMODE	index_lockmode = NoLock;
+	bool		ok = true;
 
 	/*
 	 * For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -173,14 +178,15 @@ stats_lock_check_privileges(Oid reloid)
 		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
-			ereport(ERROR,
+			ereport(WARNING,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot modify statistics for relation \"%s\"",
 							RelationGetRelationName(table)),
 					 errdetail_relkind_not_supported(table->rd_rel->relkind)));
+		ok = false;
 	}
 
-	if (OidIsValid(index_oid))
+	if (ok && (OidIsValid(index_oid)))
 	{
 		Relation	index;
 
@@ -193,25 +199,33 @@ stats_lock_check_privileges(Oid reloid)
 		relation_close(index, NoLock);
 	}
 
-	if (table->rd_rel->relisshared)
-		ereport(ERROR,
+	if (ok && (table->rd_rel->relisshared))
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics for shared relation")));
+		ok = false;
+	}
 
-	if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+	if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
 	{
 		AclResult	aclresult = pg_class_aclcheck(RelationGetRelid(table),
 												  GetUserId(),
 												  ACL_MAINTAIN);
 
 		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult,
-						   get_relkind_objtype(table->rd_rel->relkind),
-						   NameStr(table->rd_rel->relname));
+		{
+			ereport(WARNING,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						errmsg("permission denied for relation %s",
+							   NameStr(table->rd_rel->relname))));
+			ok = false;
+		}
 	}
 
 	/* retain lock on table */
 	relation_close(table, NoLock);
+	return ok;
 }
 
 /*
@@ -223,10 +237,20 @@ stats_lookup_relid(const char *nspname, const char *relname)
 	Oid			nspoid;
 	Oid			reloid;
 
-	nspoid = LookupExplicitNamespace(nspname, false);
+	nspoid = LookupExplicitNamespace(nspname, true);
+	if (!OidIsValid(nspoid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+				 errmsg("relation \"%s.%s\" does not exist",
+						nspname, relname)));
+
+		return InvalidOid;
+	}
+
 	reloid = get_relname_relid(relname, nspoid);
 	if (!OidIsValid(reloid))
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_TABLE),
 				 errmsg("relation \"%s.%s\" does not exist",
 						nspname, relname)));
@@ -303,9 +327,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 								  &args, &types, &argnulls);
 
 	if (nargs % 2 != 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				errmsg("variadic arguments must be name/value pairs"),
 				errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+		return false;
+	}
 
 	/*
 	 * For each argument name/value pair, find corresponding positional
@@ -318,14 +345,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 		char	   *argname;
 
 		if (argnulls[i])
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d is NULL", i + 1)));
+			return false;
+		}
 
 		if (types[i] != TEXTOID)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
 							i + 1, format_type_be(types[i]),
 							format_type_be(TEXTOID))));
+			return false;
+		}
 
 		if (argnulls[i + 1])
 			continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 48d6392b4ad..161cf67b711 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,49 +46,85 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 --
 -- relstats tests
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
-ERROR:  "schemaname" cannot be NULL
--- error: relname missing
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
-ERROR:  "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 WARNING:  argument "schemaname" has type "double precision", expected type "text"
-ERROR:  "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 WARNING:  argument "relname" has type "oid", expected type "text"
-ERROR:  "relname" cannot be NULL
--- error: relation not found
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
-ERROR:  relation "stats_import.nope" does not exist
--- error: odd number of variadic arguments cannot be pairs
+WARNING:  relation "stats_import.nope" does not exist
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
-ERROR:  variadic arguments must be name/value pairs
+WARNING:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 5 is NULL
+WARNING:  name at variadic position 5 is NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -340,65 +376,110 @@ CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR:  cannot modify statistics for relation "testview"
+WARNING:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 --
 -- attribute stats
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  schema "nope" does not exist
--- error: relname missing
+WARNING:  relation "nope.test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: relname does not exist
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  relation "stats_import.nope" does not exist
--- error: relname null
+WARNING:  relation "stats_import.nope" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: NULL attname
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attname doesn't exist
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -407,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "test" does not exist
--- error: both attname and attnum
+WARNING:  column "nope" of relation "test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -416,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING:  cannot specify both attname and attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attribute is system column
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING:  cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
-ERROR:  "inherited" cannot be NULL
+WARNING:  "inherited" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index d140733a750..be8045ceea5 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 -- relstats tests
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
 
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 
--- error: relation not found
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
 
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
 -- attribute stats
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: NULL attname
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
 
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: inherited null
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
-- 
2.49.0



  [text/x-patch] v10-0001-Introduce-CreateStmtPtr.patch (17.2K, ../../CADkLM=ftC94muGKH+z1irdBUXO2+tmBMLdqDAAxcct=H+LE1Eg@mail.gmail.com/5-v10-0001-Introduce-CreateStmtPtr.patch)
  download | inline diff:
From 8611beb5a7906d0f7e93fb68aa41dd58bc7ab80f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v10 1/4] Introduce CreateStmtPtr.

CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.

Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
 src/bin/pg_dump/pg_backup.h          |   2 +
 src/bin/pg_dump/pg_backup_archiver.c |  22 ++-
 src/bin/pg_dump/pg_backup_archiver.h |   7 +
 src/bin/pg_dump/pg_dump.c            | 230 +++++++++++++++------------
 4 files changed, 156 insertions(+), 105 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 658986de6f8..fdcccd64a70 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -289,6 +289,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
 
 typedef void (*SetupWorkerPtrType) (Archive *AH);
 
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
 /*
  * Main archiver interface.
  */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 82d51c89ac6..e512201ed58 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1264,6 +1264,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
 	newToc->dataDumper = opts->dumpFn;
 	newToc->dataDumperArg = opts->dumpArg;
 	newToc->hadDumper = opts->dumpFn ? true : false;
+	newToc->createDumper = opts->createFn;
+	newToc->createDumperArg = opts->createArg;
+	newToc->hadCreateDumper = opts->createFn ? true : false;
 
 	newToc->formatData = NULL;
 	newToc->dataLength = 0;
@@ -2620,7 +2623,17 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->tag);
 		WriteStr(AH, te->desc);
 		WriteInt(AH, te->section);
-		WriteStr(AH, te->defn);
+
+		if (te->hadCreateDumper)
+		{
+			char	   *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+			WriteStr(AH, defn);
+			pg_free(defn);
+		}
+		else
+			WriteStr(AH, te->defn);
+
 		WriteStr(AH, te->dropStmt);
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
@@ -3856,6 +3869,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
 	{
 		IssueACLPerBlob(AH, te);
 	}
+	else if (te->hadCreateDumper)
+	{
+		char	   *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+		ahwrite(ptr, 1, strlen(ptr), AH);
+		pg_free(ptr);
+	}
 	else if (te->defn && strlen(te->defn) > 0)
 	{
 		ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
 	const void *dataDumperArg;	/* Arg for above routine */
 	void	   *formatData;		/* TOC Entry data specific to file format */
 
+	CreateStmtPtr createDumper; /* Routine for create statement creation */
+	const void *createDumperArg;	/* arg for the above routine */
+	bool		hadCreateDumper;	/* Archiver was passed a create statement
+									 * routine */
+
 	/* working state while dumping/restoring */
 	pgoff_t		dataLength;		/* item's data size; 0 if none or unknown */
 	int			reqs;			/* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
 	int			nDeps;
 	DataDumperPtr dumpFn;
 	const void *dumpArg;
+	CreateStmtPtr createFn;
+	const void *createArg;
 } ArchiveOpts;
 #define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
 /* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e41e645f649..224dc8c9330 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10520,51 +10520,44 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
 }
 
 /*
- * dumpRelationStats --
+ * printDumpRelationStats --
  *
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
  */
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
 {
+	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
 	const DumpableObject *dobj = &rsinfo->dobj;
+
+	PQExpBufferData query;
+	PQExpBufferData out;
+
 	PGresult   *res;
-	PQExpBuffer query;
-	PQExpBuffer out;
-	DumpId	   *deps = NULL;
-	int			ndeps = 0;
-	int			i_attname;
-	int			i_inherited;
-	int			i_null_frac;
-	int			i_avg_width;
-	int			i_n_distinct;
-	int			i_most_common_vals;
-	int			i_most_common_freqs;
-	int			i_histogram_bounds;
-	int			i_correlation;
-	int			i_most_common_elems;
-	int			i_most_common_elem_freqs;
-	int			i_elem_count_histogram;
-	int			i_range_length_histogram;
-	int			i_range_empty_frac;
-	int			i_range_bounds_histogram;
 
-	/* nothing to do if we are not dumping statistics */
-	if (!fout->dopt->dumpStatistics)
-		return;
+	static bool first_query = true;
+	static int	i_attname;
+	static int	i_inherited;
+	static int	i_null_frac;
+	static int	i_avg_width;
+	static int	i_n_distinct;
+	static int	i_most_common_vals;
+	static int	i_most_common_freqs;
+	static int	i_histogram_bounds;
+	static int	i_correlation;
+	static int	i_most_common_elems;
+	static int	i_most_common_elem_freqs;
+	static int	i_elem_count_histogram;
+	static int	i_range_length_histogram;
+	static int	i_range_empty_frac;
+	static int	i_range_bounds_histogram;
 
-	/* dependent on the relation definition, if doing schema */
-	if (fout->dopt->dumpSchema)
+	initPQExpBuffer(&query);
+
+	if (first_query)
 	{
-		deps = dobj->dependencies;
-		ndeps = dobj->nDeps;
-	}
-
-	query = createPQExpBuffer();
-	if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
-	{
-		appendPQExpBufferStr(query,
-							 "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+		appendPQExpBufferStr(&query,
+							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
 							 "SELECT s.attname, s.inherited, "
 							 "s.null_frac, s.avg_width, s.n_distinct, "
 							 "s.most_common_vals, s.most_common_freqs, "
@@ -10573,82 +10566,85 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							 "s.elem_count_histogram, ");
 
 		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "s.range_length_histogram, "
 								 "s.range_empty_frac, "
 								 "s.range_bounds_histogram ");
 		else
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "NULL AS range_length_histogram,"
 								 "NULL AS range_empty_frac,"
 								 "NULL AS range_bounds_histogram ");
 
-		appendPQExpBufferStr(query,
+		appendPQExpBufferStr(&query,
 							 "FROM pg_catalog.pg_stats s "
 							 "WHERE s.schemaname = $1 "
 							 "AND s.tablename = $2 "
 							 "ORDER BY s.attname, s.inherited");
 
-		ExecuteSqlStatement(fout, query->data);
+		ExecuteSqlStatement(fout, query.data);
 
-		fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
-		resetPQExpBuffer(query);
+		resetPQExpBuffer(&query);
 	}
 
-	out = createPQExpBuffer();
+	initPQExpBuffer(&out);
 
 	/* restore relation stats */
-	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
+	appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'schemaname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBufferStr(out, "\t'relname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+	appendPQExpBufferStr(&out, "\t'schemaname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(&out, ",\n");
+	appendPQExpBufferStr(&out, "\t'relname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+	appendPQExpBufferStr(&out, ",\n");
+	appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+	appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+	appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
 					  rsinfo->relallvisible);
 
 	/* fetch attribute stats */
-	appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(query, ", ");
-	appendStringLiteralAH(query, dobj->name, fout);
-	appendPQExpBufferStr(query, ");");
+	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+	appendPQExpBufferStr(&query, ", ");
+	appendStringLiteralAH(&query, dobj->name, fout);
+	appendPQExpBufferStr(&query, ")");
 
-	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
 
-	i_attname = PQfnumber(res, "attname");
-	i_inherited = PQfnumber(res, "inherited");
-	i_null_frac = PQfnumber(res, "null_frac");
-	i_avg_width = PQfnumber(res, "avg_width");
-	i_n_distinct = PQfnumber(res, "n_distinct");
-	i_most_common_vals = PQfnumber(res, "most_common_vals");
-	i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-	i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-	i_correlation = PQfnumber(res, "correlation");
-	i_most_common_elems = PQfnumber(res, "most_common_elems");
-	i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-	i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-	i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-	i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-	i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+	if (first_query)
+	{
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		first_query = false;
+	}
 
 	/* restore attribute stats */
 	for (int rownum = 0; rownum < PQntuples(res); rownum++)
 	{
 		const char *attname;
 
-		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'schemaname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(out, ",\n\t'relname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+		appendPQExpBufferStr(&out, "\t'schemaname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(&out, ",\n\t'relname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10661,8 +10657,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 */
 		if (rsinfo->nindAttNames == 0)
 		{
-			appendPQExpBuffer(out, ",\n\t'attname', ");
-			appendStringLiteralAH(out, attname, fout);
+			appendPQExpBuffer(&out, ",\n\t'attname', ");
+			appendStringLiteralAH(&out, attname, fout);
 		}
 		else
 		{
@@ -10672,7 +10668,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 			{
 				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
 				{
-					appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
 									  i + 1);
 					found = true;
 					break;
@@ -10684,67 +10680,93 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		}
 
 		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(out, fout, "inherited", "boolean",
+			appendNamedArgument(&out, fout, "inherited", "boolean",
 								PQgetvalue(res, rownum, i_inherited));
 		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(out, fout, "null_frac", "real",
+			appendNamedArgument(&out, fout, "null_frac", "real",
 								PQgetvalue(res, rownum, i_null_frac));
 		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(out, fout, "avg_width", "integer",
+			appendNamedArgument(&out, fout, "avg_width", "integer",
 								PQgetvalue(res, rownum, i_avg_width));
 		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(out, fout, "n_distinct", "real",
+			appendNamedArgument(&out, fout, "n_distinct", "real",
 								PQgetvalue(res, rownum, i_n_distinct));
 		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(out, fout, "most_common_vals", "text",
+			appendNamedArgument(&out, fout, "most_common_vals", "text",
 								PQgetvalue(res, rownum, i_most_common_vals));
 		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_freqs));
 		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(out, fout, "histogram_bounds", "text",
+			appendNamedArgument(&out, fout, "histogram_bounds", "text",
 								PQgetvalue(res, rownum, i_histogram_bounds));
 		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(out, fout, "correlation", "real",
+			appendNamedArgument(&out, fout, "correlation", "real",
 								PQgetvalue(res, rownum, i_correlation));
 		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(out, fout, "most_common_elems", "text",
+			appendNamedArgument(&out, fout, "most_common_elems", "text",
 								PQgetvalue(res, rownum, i_most_common_elems));
 		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_elem_freqs));
 		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
 								PQgetvalue(res, rownum, i_elem_count_histogram));
 		if (fout->remoteVersion >= 170000)
 		{
 			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(out, fout, "range_length_histogram", "text",
+				appendNamedArgument(&out, fout, "range_length_histogram", "text",
 									PQgetvalue(res, rownum, i_range_length_histogram));
 			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(out, fout, "range_empty_frac", "real",
+				appendNamedArgument(&out, fout, "range_empty_frac", "real",
 									PQgetvalue(res, rownum, i_range_empty_frac));
 			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
 									PQgetvalue(res, rownum, i_range_bounds_histogram));
 		}
-		appendPQExpBufferStr(out, "\n);\n");
+		appendPQExpBufferStr(&out, "\n);\n");
 	}
 
 	PQclear(res);
 
+	termPQExpBuffer(&query);
+	return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+	const DumpableObject *dobj = &rsinfo->dobj;
+
+	DumpId	   *deps = NULL;
+	int			ndeps = 0;
+
+	/* nothing to do if we are not dumping statistics */
+	if (!fout->dopt->dumpStatistics)
+		return;
+
+	/* dependent on the relation definition, if doing schema */
+	if (fout->dopt->dumpSchema)
+	{
+		deps = dobj->dependencies;
+		ndeps = dobj->nDeps;
+	}
+
 	ArchiveEntry(fout, nilCatalogId, createDumpId(),
 				 ARCHIVE_OPTS(.tag = dobj->name,
 							  .namespace = dobj->namespace->dobj.name,
 							  .description = "STATISTICS DATA",
 							  .section = rsinfo->postponed_def ?
 							  SECTION_POST_DATA : statisticsDumpSection(rsinfo),
-							  .createStmt = out->data,
+							  .createFn = printRelationStats,
+							  .createArg = rsinfo,
 							  .deps = deps,
 							  .nDeps = ndeps));
-
-	destroyPQExpBuffer(out);
-	destroyPQExpBuffer(query);
 }
 
 /*

base-commit: bde2fb797aaebcbe06bf60f330ba5a068f17dda7
-- 
2.49.0



  [text/x-patch] v10-0003-Add-relallfrozen-to-pg_dump-statistics.patch (7.9K, ../../CADkLM=ftC94muGKH+z1irdBUXO2+tmBMLdqDAAxcct=H+LE1Eg@mail.gmail.com/6-v10-0003-Add-relallfrozen-to-pg_dump-statistics.patch)
  download | inline diff:
From 87da7d4c517c3b2e63666892e64b9de2a8dbbe44 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 15 Mar 2025 17:34:30 -0400
Subject: [PATCH v10 3/4] Add relallfrozen to pg_dump statistics.

The column relallfrozen was recently added to pg_class and it also
represent statistics, so we should add it to the dump/restore/upgrade
operations.

Dumps of databases prior to v18 will not attempt to restore any value to
relallfrozen, allowing pg_restore_relation_stats() to set the default it
deems appropriate.
---
 src/bin/pg_dump/pg_dump.c        | 52 ++++++++++++++++++++++----------
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl |  3 +-
 3 files changed, 39 insertions(+), 17 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e3f2dac33ec..6c366fd55d3 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6897,7 +6897,8 @@ getFuncs(Archive *fout)
  */
 static RelStatsInfo *
 getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
-					  char *reltuples, int32 relallvisible, char relkind,
+					  char *reltuples, int32 relallvisible,
+					  int32 relallfrozen, char relkind,
 					  char **indAttNames, int nindAttNames)
 {
 	if (!fout->dopt->dumpStatistics)
@@ -6926,6 +6927,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
 		info->relpages = relpages;
 		info->reltuples = pstrdup(reltuples);
 		info->relallvisible = relallvisible;
+		info->relallfrozen = relallfrozen;
 		info->relkind = relkind;
 		info->indAttNames = indAttNames;
 		info->nindAttNames = nindAttNames;
@@ -6965,6 +6967,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relpages;
 	int			i_reltuples;
 	int			i_relallvisible;
+	int			i_relallfrozen;
 	int			i_toastpages;
 	int			i_owning_tab;
 	int			i_owning_col;
@@ -7015,8 +7018,13 @@ getTables(Archive *fout, int *numTables)
 						 "c.relowner, "
 						 "c.relchecks, "
 						 "c.relhasindex, c.relhasrules, c.relpages, "
-						 "c.reltuples, c.relallvisible, c.relhastriggers, "
-						 "c.relpersistence, "
+						 "c.reltuples, c.relallvisible, ");
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBufferStr(query, "c.relallfrozen, ");
+
+	appendPQExpBufferStr(query,
+						 "c.relhastriggers, c.relpersistence, "
 						 "c.reloftype, "
 						 "c.relacl, "
 						 "acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
@@ -7181,6 +7189,7 @@ getTables(Archive *fout, int *numTables)
 	i_relpages = PQfnumber(res, "relpages");
 	i_reltuples = PQfnumber(res, "reltuples");
 	i_relallvisible = PQfnumber(res, "relallvisible");
+	i_relallfrozen = PQfnumber(res, "relallfrozen");
 	i_toastpages = PQfnumber(res, "toastpages");
 	i_owning_tab = PQfnumber(res, "owning_tab");
 	i_owning_col = PQfnumber(res, "owning_col");
@@ -7228,6 +7237,7 @@ getTables(Archive *fout, int *numTables)
 	for (i = 0; i < ntups; i++)
 	{
 		int32		relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+		int32		relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen));
 
 		tblinfo[i].dobj.objType = DO_TABLE;
 		tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
@@ -7330,7 +7340,7 @@ getTables(Archive *fout, int *numTables)
 		if (tblinfo[i].interesting)
 			getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages,
 								  PQgetvalue(res, i, i_reltuples),
-								  relallvisible, tblinfo[i].relkind, NULL, 0);
+								  relallvisible, relallfrozen, tblinfo[i].relkind, NULL, 0);
 
 		/*
 		 * Read-lock target tables to make sure they aren't DROPPED or altered
@@ -7599,6 +7609,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_relpages,
 				i_reltuples,
 				i_relallvisible,
+				i_relallfrozen,
 				i_parentidx,
 				i_indexdef,
 				i_indnkeyatts,
@@ -7653,7 +7664,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	appendPQExpBufferStr(query,
 						 "SELECT t.tableoid, t.oid, i.indrelid, "
 						 "t.relname AS indexname, "
-						 "t.relpages, t.reltuples, t.relallvisible, "
+						 "t.relpages, t.reltuples, t.relallvisible, ");
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBufferStr(query, "t.relallfrozen, ");
+
+	appendPQExpBufferStr(query,
 						 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
 						 "i.indkey, i.indisclustered, "
 						 "c.contype, c.conname, "
@@ -7769,6 +7785,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	i_relpages = PQfnumber(res, "relpages");
 	i_reltuples = PQfnumber(res, "reltuples");
 	i_relallvisible = PQfnumber(res, "relallvisible");
+	i_relallfrozen = PQfnumber(res, "relallfrozen");
 	i_parentidx = PQfnumber(res, "parentidx");
 	i_indexdef = PQfnumber(res, "indexdef");
 	i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7840,6 +7857,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			RelStatsInfo *relstats;
 			int32		relpages = atoi(PQgetvalue(res, j, i_relpages));
 			int32		relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
+			int32		relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen));
 
 			indxinfo[j].dobj.objType = DO_INDEX;
 			indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7882,7 +7900,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 
 			relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
 											 PQgetvalue(res, j, i_reltuples),
-											 relallvisible, indexkind,
+											 relallvisible, relallfrozen, indexkind,
 											 indAttNames, nindAttNames);
 
 			contype = *(PQgetvalue(res, j, i_contype));
@@ -10862,19 +10880,21 @@ printRelationStats(Archive *fout, const void *userArg)
 	initPQExpBuffer(&out);
 
 	/* restore relation stats */
-	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-	appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
+	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(");
+	appendPQExpBuffer(&out, "\n\t'version', '%u'::integer",
 					  fout->remoteVersion);
-	appendPQExpBufferStr(&out, "\t'schemaname', ");
+	appendPQExpBufferStr(&out, ",\n\t'schemaname', ");
 	appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
-	appendPQExpBufferStr(&out, ",\n");
-	appendPQExpBufferStr(&out, "\t'relname', ");
+	appendPQExpBufferStr(&out, ",\n\t'relname', ");
 	appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-	appendPQExpBufferStr(&out, ",\n");
-	appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n",
-					  rsinfo->relallvisible);
+	appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages);
+	appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples);
+	appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible);
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+
+	appendPQExpBufferStr(&out, "\n);\n");
 
 	AH->txnCount++;
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index bbdb30b5f54..82f1eb3c4b7 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -441,6 +441,7 @@ typedef struct _relStatsInfo
 	int32		relpages;
 	char	   *reltuples;
 	int32		relallvisible;
+	int32		relallfrozen;
 	char		relkind;		/* 'r', 'm', 'i', etc */
 
 	/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 51ebf8ad13c..576326daec7 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4771,7 +4771,8 @@ my %tests = (
 			'relname',\s'dup_test_post_data_ix',\s+
 			'relpages',\s'\d+'::integer,\s+
 			'reltuples',\s'\d+'::real,\s+
-			'relallvisible',\s'\d+'::integer\s+
+			'relallvisible',\s'\d+'::integer,\s+
+			'relallfrozen',\s'\d+'::integer\s+
 			\);\s+
 			\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
-- 
2.49.0



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-29 01:11                               ` Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-29 01:11 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

A rebase and a reordering of the commits to put the really-really-must-have
relallfrozen ahead of the really-must-have stats batching and both of them
head of the error->warning step-downs.


Attachments:

  [text/x-patch] v11-0001-Add-relallfrozen-to-pg_dump-statistics.patch (7.2K, ../../CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com/3-v11-0001-Add-relallfrozen-to-pg_dump-statistics.patch)
  download | inline diff:
From 96b10b1eb955c5619d23cadf7de8b12d2db638a9 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 15 Mar 2025 17:34:30 -0400
Subject: [PATCH v11 1/4] Add relallfrozen to pg_dump statistics.

The column relallfrozen was recently added to pg_class and it also
represent statistics, so we should add it to the dump/restore/upgrade
operations.

Dumps of databases prior to v18 will not attempt to restore any value to
relallfrozen, allowing pg_restore_relation_stats() to set the default it
deems appropriate.
---
 src/bin/pg_dump/pg_dump.c        | 38 ++++++++++++++++++++++++++------
 src/bin/pg_dump/pg_dump.h        |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl |  3 ++-
 3 files changed, 34 insertions(+), 8 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 84a78625820..211cf10dbd6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6874,7 +6874,8 @@ getFuncs(Archive *fout)
  */
 static RelStatsInfo *
 getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
-					  char *reltuples, int32 relallvisible, char relkind,
+					  char *reltuples, int32 relallvisible,
+					  int32 relallfrozen, char relkind,
 					  char **indAttNames, int nindAttNames)
 {
 	if (!fout->dopt->dumpStatistics)
@@ -6903,6 +6904,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
 		info->relpages = relpages;
 		info->reltuples = pstrdup(reltuples);
 		info->relallvisible = relallvisible;
+		info->relallfrozen = relallfrozen;
 		info->relkind = relkind;
 		info->indAttNames = indAttNames;
 		info->nindAttNames = nindAttNames;
@@ -6967,6 +6969,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relpages;
 	int			i_reltuples;
 	int			i_relallvisible;
+	int			i_relallfrozen;
 	int			i_toastpages;
 	int			i_owning_tab;
 	int			i_owning_col;
@@ -7017,8 +7020,13 @@ getTables(Archive *fout, int *numTables)
 						 "c.relowner, "
 						 "c.relchecks, "
 						 "c.relhasindex, c.relhasrules, c.relpages, "
-						 "c.reltuples, c.relallvisible, c.relhastriggers, "
-						 "c.relpersistence, "
+						 "c.reltuples, c.relallvisible, ");
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBufferStr(query, "c.relallfrozen, ");
+
+	appendPQExpBufferStr(query,
+						 "c.relhastriggers, c.relpersistence, "
 						 "c.reloftype, "
 						 "c.relacl, "
 						 "acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
@@ -7183,6 +7191,7 @@ getTables(Archive *fout, int *numTables)
 	i_relpages = PQfnumber(res, "relpages");
 	i_reltuples = PQfnumber(res, "reltuples");
 	i_relallvisible = PQfnumber(res, "relallvisible");
+	i_relallfrozen = PQfnumber(res, "relallfrozen");
 	i_toastpages = PQfnumber(res, "toastpages");
 	i_owning_tab = PQfnumber(res, "owning_tab");
 	i_owning_col = PQfnumber(res, "owning_col");
@@ -7230,6 +7239,7 @@ getTables(Archive *fout, int *numTables)
 	for (i = 0; i < ntups; i++)
 	{
 		int32		relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+		int32		relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen));
 
 		tblinfo[i].dobj.objType = DO_TABLE;
 		tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
@@ -7336,7 +7346,7 @@ getTables(Archive *fout, int *numTables)
 			stats = getRelationStatistics(fout, &tblinfo[i].dobj,
 										  tblinfo[i].relpages,
 										  PQgetvalue(res, i, i_reltuples),
-										  relallvisible,
+										  relallvisible, relallfrozen,
 										  tblinfo[i].relkind, NULL, 0);
 			if (tblinfo[i].relkind == RELKIND_MATVIEW)
 				tblinfo[i].stats = stats;
@@ -7609,6 +7619,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_relpages,
 				i_reltuples,
 				i_relallvisible,
+				i_relallfrozen,
 				i_parentidx,
 				i_indexdef,
 				i_indnkeyatts,
@@ -7663,7 +7674,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	appendPQExpBufferStr(query,
 						 "SELECT t.tableoid, t.oid, i.indrelid, "
 						 "t.relname AS indexname, "
-						 "t.relpages, t.reltuples, t.relallvisible, "
+						 "t.relpages, t.reltuples, t.relallvisible, ");
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBufferStr(query, "t.relallfrozen, ");
+
+	appendPQExpBufferStr(query,
 						 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
 						 "i.indkey, i.indisclustered, "
 						 "c.contype, c.conname, "
@@ -7779,6 +7795,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	i_relpages = PQfnumber(res, "relpages");
 	i_reltuples = PQfnumber(res, "reltuples");
 	i_relallvisible = PQfnumber(res, "relallvisible");
+	i_relallfrozen = PQfnumber(res, "relallfrozen");
 	i_parentidx = PQfnumber(res, "parentidx");
 	i_indexdef = PQfnumber(res, "indexdef");
 	i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7850,6 +7867,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			RelStatsInfo *relstats;
 			int32		relpages = atoi(PQgetvalue(res, j, i_relpages));
 			int32		relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
+			int32		relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen));
 
 			indxinfo[j].dobj.objType = DO_INDEX;
 			indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7892,7 +7910,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 
 			relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
 											 PQgetvalue(res, j, i_reltuples),
-											 relallvisible, indexkind,
+											 relallvisible, relallfrozen, indexkind,
 											 indAttNames, nindAttNames);
 
 			contype = *(PQgetvalue(res, j, i_contype));
@@ -10618,9 +10636,15 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 	appendPQExpBufferStr(out, ",\n");
 	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
 	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
 					  rsinfo->relallvisible);
 
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBuffer(out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+
+	appendPQExpBufferStr(out, "\n);\n");
+
+
 	/* fetch attribute stats */
 	appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
 	appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 70f7a369e4a..e6f0f86a459 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -442,6 +442,7 @@ typedef struct _relStatsInfo
 	int32		relpages;
 	char	   *reltuples;
 	int32		relallvisible;
+	int32		relallfrozen;
 	char		relkind;		/* 'r', 'm', 'i', etc */
 
 	/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 51ebf8ad13c..576326daec7 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4771,7 +4771,8 @@ my %tests = (
 			'relname',\s'dup_test_post_data_ix',\s+
 			'relpages',\s'\d+'::integer,\s+
 			'reltuples',\s'\d+'::real,\s+
-			'relallvisible',\s'\d+'::integer\s+
+			'relallvisible',\s'\d+'::integer,\s+
+			'relallfrozen',\s'\d+'::integer\s+
 			\);\s+
 			\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
 			'version',\s'\d+'::integer,\s+

base-commit: a0a4601765b896079eb82a9d5cfa1f41154fcfdb
-- 
2.49.0



  [text/x-patch] v11-0004-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch (30.4K, ../../CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com/4-v11-0004-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch)
  download | inline diff:
From fd6cd3691e21b807a299749631dc0bdddc886853 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v11 4/4] Downgrade many pg_restore_*_stats errors to warnings.

We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
 src/include/statistics/stat_utils.h        |   4 +-
 src/backend/statistics/attribute_stats.c   | 120 ++++++++++----
 src/backend/statistics/relation_stats.c    |  12 +-
 src/backend/statistics/stat_utils.c        |  65 ++++++--
 src/test/regress/expected/stats_import.out | 184 ++++++++++++++++-----
 src/test/regress/sql/stats_import.sql      |  36 ++--
 6 files changed, 309 insertions(+), 112 deletions(-)

diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 512eb776e0e..809c8263a41 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
 	Oid			argtype;
 };
 
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
 									 struct StatsArgInfo *arginfo,
 									 int argnum);
 extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 								 struct StatsArgInfo *arginfo,
 								 int argnum1, int argnum2);
 
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
 
 extern Oid	stats_lookup_relid(const char *nspname, const char *relname);
 
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f5eb17ba42d..b7ba1622391 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
 
 static bool attribute_statistics_update(FunctionCallInfo fcinfo);
 static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
 							   Oid *atttypid, int32 *atttypmod,
 							   char *atttyptype, Oid *atttypcoll,
 							   Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
  * stored as an anyarray, and the representation of the array needs to store
  * the correct element type, which must be derived from the attribute.
  *
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
  */
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -148,8 +150,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	HeapTuple	statup;
 
 	Oid			atttypid = InvalidOid;
-	int32		atttypmod;
-	char		atttyptype;
+	int32		atttypmod = -1;
+	char		atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
 	Oid			atttypcoll = InvalidOid;
 	Oid			eq_opr = InvalidOid;
 	Oid			lt_opr = InvalidOid;
@@ -176,38 +178,52 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+		return false;
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		return false;
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		return false;
+	}
 
 	/* lock before looking up attribute */
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
 		if (!PG_ARGISNULL(ATTNUM_ARG))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
+			return false;
+		}
 		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" of relation \"%s\" does not exist",
 							attname, relname)));
+			return false;
+		}
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -216,27 +232,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		/* annoyingly, get_attname doesn't check attisdropped */
 		if (attname == NULL ||
 			!SearchSysCacheExistsAttName(reloid, attname))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column %d of relation \"%s\" does not exist",
 							attnum, relname)));
+			return false;
+		}
 	}
 	else
 	{
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("must specify either attname or attnum")));
-		attname = NULL;			/* keep compiler quiet */
-		attnum = 0;
+		return false;
 	}
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
+		return false;
+	}
 
-	stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+		return false;
 	inherited = PG_GETARG_BOOL(INHERITED_ARG);
 
 	/*
@@ -285,10 +307,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* derive information from attribute */
-	get_attr_stat_type(reloid, attnum,
-					   &atttypid, &atttypmod,
-					   &atttyptype, &atttypcoll,
-					   &eq_opr, &lt_opr);
+	if (!get_attr_stat_type(reloid, attnum,
+							&atttypid, &atttypmod,
+							&atttyptype, &atttypcoll,
+							&eq_opr, &lt_opr))
+		result = false;
 
 	/* if needed, derive element type */
 	if (do_mcelem || do_dechist)
@@ -568,7 +591,7 @@ get_attr_expr(Relation rel, int attnum)
 /*
  * Derive type information from the attribute.
  */
-static void
+static bool
 get_attr_stat_type(Oid reloid, AttrNumber attnum,
 				   Oid *atttypid, int32 *atttypmod,
 				   char *atttyptype, Oid *atttypcoll,
@@ -585,18 +608,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 
 	/* Attribute not found */
 	if (!HeapTupleIsValid(atup))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	attr = (Form_pg_attribute) GETSTRUCT(atup);
 
 	if (attr->attisdropped)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	expr = get_attr_expr(rel, attr->attnum);
 
@@ -645,6 +676,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 		*atttypcoll = DEFAULT_COLLATION_OID;
 
 	relation_close(rel, NoLock);
+	return true;
 }
 
 /*
@@ -770,6 +802,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
 	if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
 		slotidx = first_empty;
 
+	/*
+	 * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+	 * statistic kinds, so this can safely remain an ERROR for now.
+	 */
 	if (slotidx >= STATISTIC_NUM_SLOTS)
 		ereport(ERROR,
 				(errmsg("maximum number of statistics slots exceeded: %d",
@@ -915,38 +951,54 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+		PG_RETURN_VOID();
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		PG_RETURN_VOID();
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		PG_RETURN_VOID();
+	}
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		PG_RETURN_VOID();
 
 	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
 	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
 						attname)));
+		PG_RETURN_VOID();
+	}
 
 	if (attnum == InvalidAttrNumber)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
 						attname, get_rel_name(reloid))));
+		PG_RETURN_VOID();
+	}
 
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index cd3a75b621a..7c47af15c9f 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -83,13 +83,18 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
-	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+		return false;
+
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		return false;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -97,7 +102,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index a9a3224efe6..d587e875457 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -33,16 +33,20 @@
 /*
  * Ensure that a given argument is not null.
  */
-void
+bool
 stats_check_required_arg(FunctionCallInfo fcinfo,
 						 struct StatsArgInfo *arginfo,
 						 int argnum)
 {
 	if (PG_ARGISNULL(argnum))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("\"%s\" cannot be NULL",
 						arginfo[argnum].argname)));
+		return false;
+	}
+	return true;
 }
 
 /*
@@ -127,13 +131,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
  *   - the role owns the current database and the relation is not shared
  *   - the role has the MAINTAIN privilege on the relation
  */
-void
+bool
 stats_lock_check_privileges(Oid reloid)
 {
 	Relation	table;
 	Oid			table_oid = reloid;
 	Oid			index_oid = InvalidOid;
 	LOCKMODE	index_lockmode = NoLock;
+	bool		ok = true;
 
 	/*
 	 * For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -173,14 +178,15 @@ stats_lock_check_privileges(Oid reloid)
 		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
-			ereport(ERROR,
+			ereport(WARNING,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot modify statistics for relation \"%s\"",
 							RelationGetRelationName(table)),
 					 errdetail_relkind_not_supported(table->rd_rel->relkind)));
+		ok = false;
 	}
 
-	if (OidIsValid(index_oid))
+	if (ok && (OidIsValid(index_oid)))
 	{
 		Relation	index;
 
@@ -193,25 +199,33 @@ stats_lock_check_privileges(Oid reloid)
 		relation_close(index, NoLock);
 	}
 
-	if (table->rd_rel->relisshared)
-		ereport(ERROR,
+	if (ok && (table->rd_rel->relisshared))
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics for shared relation")));
+		ok = false;
+	}
 
-	if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+	if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
 	{
 		AclResult	aclresult = pg_class_aclcheck(RelationGetRelid(table),
 												  GetUserId(),
 												  ACL_MAINTAIN);
 
 		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult,
-						   get_relkind_objtype(table->rd_rel->relkind),
-						   NameStr(table->rd_rel->relname));
+		{
+			ereport(WARNING,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						errmsg("permission denied for relation %s",
+							   NameStr(table->rd_rel->relname))));
+			ok = false;
+		}
 	}
 
 	/* retain lock on table */
 	relation_close(table, NoLock);
+	return ok;
 }
 
 /*
@@ -223,10 +237,20 @@ stats_lookup_relid(const char *nspname, const char *relname)
 	Oid			nspoid;
 	Oid			reloid;
 
-	nspoid = LookupExplicitNamespace(nspname, false);
+	nspoid = LookupExplicitNamespace(nspname, true);
+	if (!OidIsValid(nspoid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+				 errmsg("relation \"%s.%s\" does not exist",
+						nspname, relname)));
+
+		return InvalidOid;
+	}
+
 	reloid = get_relname_relid(relname, nspoid);
 	if (!OidIsValid(reloid))
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_TABLE),
 				 errmsg("relation \"%s.%s\" does not exist",
 						nspname, relname)));
@@ -303,9 +327,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 								  &args, &types, &argnulls);
 
 	if (nargs % 2 != 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				errmsg("variadic arguments must be name/value pairs"),
 				errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+		return false;
+	}
 
 	/*
 	 * For each argument name/value pair, find corresponding positional
@@ -318,14 +345,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 		char	   *argname;
 
 		if (argnulls[i])
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d is NULL", i + 1)));
+			return false;
+		}
 
 		if (types[i] != TEXTOID)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
 							i + 1, format_type_be(types[i]),
 							format_type_be(TEXTOID))));
+			return false;
+		}
 
 		if (argnulls[i + 1])
 			continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 48d6392b4ad..161cf67b711 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,49 +46,85 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 --
 -- relstats tests
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
-ERROR:  "schemaname" cannot be NULL
--- error: relname missing
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
-ERROR:  "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 WARNING:  argument "schemaname" has type "double precision", expected type "text"
-ERROR:  "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 WARNING:  argument "relname" has type "oid", expected type "text"
-ERROR:  "relname" cannot be NULL
--- error: relation not found
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
-ERROR:  relation "stats_import.nope" does not exist
--- error: odd number of variadic arguments cannot be pairs
+WARNING:  relation "stats_import.nope" does not exist
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
-ERROR:  variadic arguments must be name/value pairs
+WARNING:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 5 is NULL
+WARNING:  name at variadic position 5 is NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -340,65 +376,110 @@ CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR:  cannot modify statistics for relation "testview"
+WARNING:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 --
 -- attribute stats
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  schema "nope" does not exist
--- error: relname missing
+WARNING:  relation "nope.test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: relname does not exist
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  relation "stats_import.nope" does not exist
--- error: relname null
+WARNING:  relation "stats_import.nope" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: NULL attname
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attname doesn't exist
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -407,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "test" does not exist
--- error: both attname and attnum
+WARNING:  column "nope" of relation "test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -416,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING:  cannot specify both attname and attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attribute is system column
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING:  cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
-ERROR:  "inherited" cannot be NULL
+WARNING:  "inherited" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index d140733a750..be8045ceea5 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 -- relstats tests
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
 
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 
--- error: relation not found
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
 
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
 -- attribute stats
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: NULL attname
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
 
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: inherited null
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
-- 
2.49.0



  [text/x-patch] v11-0003-Batching-getAttributeStats.patch (21.5K, ../../CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com/5-v11-0003-Batching-getAttributeStats.patch)
  download | inline diff:
From 7b226732d1e68b5899c3dc8fbc6eb940c0f884ad Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v11 3/4] Batching getAttributeStats().

The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.

The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
 src/bin/pg_dump/pg_dump.c | 554 ++++++++++++++++++++++++++------------
 1 file changed, 383 insertions(+), 171 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b7571ea15eb..bcf9dd1eb47 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
 	zeroAsNone = 4,
 } OidOptions;
 
+typedef enum StatsBufferState
+{
+	STATSBUF_UNINITIALIZED = 0,
+	STATSBUF_ACTIVE,
+	STATSBUF_EXHAUSTED
+}			StatsBufferState;
+
+typedef struct
+{
+	PGresult   *res;			/* results from most recent
+								 * getAttributeStats() */
+	int			idx;			/* first un-consumed row of results */
+	TocEntry   *te;				/* next TOC entry to search for statsitics
+								 * data */
+
+	StatsBufferState state;		/* current state of the buffer */
+}			AttributeStatsBuffer;
+
+
 /* global decls */
 static bool dosync = true;		/* Issue fsync() to make dump durable on disk. */
 
@@ -209,6 +228,18 @@ static int	nbinaryUpgradeClassOids = 0;
 static SequenceItem *sequences = NULL;
 static int	nsequences = 0;
 
+static AttributeStatsBuffer attrstats =
+{
+	NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
 /*
  * The default number of rows per INSERT when
  * --inserts is specified without --rows-per-insert
@@ -222,6 +253,8 @@ static int	nsequences = 0;
  */
 #define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
 
+
+
 /*
  * Macro for producing quoted, schema-qualified name of a dumpable object.
  */
@@ -399,6 +432,9 @@ static void setupDumpWorker(Archive *AH);
 static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
 static bool forcePartitionRootLoad(const TableInfo *tbinfo);
 static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+								const char *argname, const char *argtype,
+								const char *argval);
 
 
 int
@@ -10556,7 +10592,286 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
 }
 
 /*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData schemas;
+	PQExpBufferData relations;
+	int			numoids = 0;
+
+	Assert(AH != NULL);
+
+	/* free last result set, if any */
+	if (attrstats.state == STATSBUF_ACTIVE)
+		PQclear(attrstats.res);
+
+	/* If we have looped around to the start of the TOC, restart */
+	if (attrstats.te == AH->toc)
+		attrstats.te = AH->toc->next;
+
+	initPQExpBuffer(&schemas);
+	initPQExpBuffer(&relations);
+
+	/*
+	 * Walk ahead looking for relstats entries that are active in this
+	 * section, adding the names to the schemas and relations lists.
+	 */
+	while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+	{
+		if (attrstats.te->reqs != 0 &&
+			strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+		{
+			RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+			Assert(rsinfo != NULL);
+
+			if (numoids > 0)
+			{
+				appendPQExpBufferStr(&schemas, ",");
+				appendPQExpBufferStr(&relations, ",");
+			}
+			appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+			numoids++;
+		}
+
+		attrstats.te = attrstats.te->next;
+	}
+
+	if (numoids > 0)
+	{
+		PQExpBufferData query;
+
+		initPQExpBuffer(&query);
+		appendPQExpBuffer(&query,
+						  "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+						  schemas.data, relations.data);
+		attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+		attrstats.idx = 0;
+	}
+	else
+	{
+		attrstats.state = STATSBUF_EXHAUSTED;
+		attrstats.res = NULL;
+		attrstats.idx = -1;
+	}
+
+	termPQExpBuffer(&schemas);
+	termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData query;
+
+	Assert(AH != NULL);
+	initPQExpBuffer(&query);
+
+	appendPQExpBufferStr(&query,
+						 "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+						 "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+						 "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+						 "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+						 "s.most_common_elems, s.most_common_elem_freqs, "
+						 "s.elem_count_histogram, ");
+
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(&query,
+							 "s.range_length_histogram, "
+							 "s.range_empty_frac, "
+							 "s.range_bounds_histogram ");
+	else
+		appendPQExpBufferStr(&query,
+							 "NULL AS range_length_histogram, "
+							 "NULL AS range_empty_frac, "
+							 " NULL AS range_bounds_histogram ");
+
+	/*
+	 * The results must be in the order of relations supplied in the
+	 * parameters to ensure that they are in sync with a walk of the TOC.
+	 *
+	 * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+	 * is a way to lead the query into using the index
+	 * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+	 * expensive full scan of pg_stats.
+	 *
+	 * We may need to adjust this query for versions that are not so easily
+	 * led.
+	 */
+	appendPQExpBufferStr(&query,
+						 "FROM pg_catalog.pg_stats AS s "
+						 "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+						 "ON s.schemaname = u.schemaname "
+						 "AND s.tablename = u.tablename "
+						 "WHERE s.tablename = ANY($2) "
+						 "ORDER BY u.ord, s.attname, s.inherited");
+
+	ExecuteSqlStatement(fout, query.data);
+
+	termPQExpBuffer(&query);
+
+	attrstats.te = AH->toc->next;
+
+	fetchNextAttributeStats(fout);
+
+	attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+					 const RelStatsInfo *rsinfo)
+{
+	PGresult   *res = attrstats.res;
+	int			tup_num = attrstats.idx;
+
+	const char *attname;
+
+	static bool indexes_set = false;
+	static int	i_attname,
+				i_inherited,
+				i_null_frac,
+				i_avg_width,
+				i_n_distinct,
+				i_most_common_vals,
+				i_most_common_freqs,
+				i_histogram_bounds,
+				i_correlation,
+				i_most_common_elems,
+				i_most_common_elem_freqs,
+				i_elem_count_histogram,
+				i_range_length_histogram,
+				i_range_empty_frac,
+				i_range_bounds_histogram;
+
+	if (!indexes_set)
+	{
+		/*
+		 * It's a prepared statement, so the indexes will be the same for all
+		 * result sets, so we only need to set them once.
+		 */
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		indexes_set = true;
+	}
+
+	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+					  fout->remoteVersion);
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+	if (PQgetisnull(res, tup_num, i_attname))
+		pg_fatal("attname cannot be NULL");
+	attname = PQgetvalue(res, tup_num, i_attname);
+
+	/*
+	 * Indexes look up attname in indAttNames to derive attnum, all others use
+	 * attname directly.  We must specify attnum for indexes, since their
+	 * attnames are not necessarily stable across dump/reload.
+	 */
+	if (rsinfo->nindAttNames == 0)
+	{
+		appendPQExpBuffer(out, ",\n\t'attname', ");
+		appendStringLiteralAH(out, attname, fout);
+	}
+	else
+	{
+		bool		found = false;
+
+		for (int i = 0; i < rsinfo->nindAttNames; i++)
+			if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+			{
+				appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+								  i + 1);
+				found = true;
+				break;
+			}
+
+		if (!found)
+			pg_fatal("could not find index attname \"%s\"", attname);
+	}
+
+	if (!PQgetisnull(res, tup_num, i_inherited))
+		appendNamedArgument(out, fout, "inherited", "boolean",
+							PQgetvalue(res, tup_num, i_inherited));
+	if (!PQgetisnull(res, tup_num, i_null_frac))
+		appendNamedArgument(out, fout, "null_frac", "real",
+							PQgetvalue(res, tup_num, i_null_frac));
+	if (!PQgetisnull(res, tup_num, i_avg_width))
+		appendNamedArgument(out, fout, "avg_width", "integer",
+							PQgetvalue(res, tup_num, i_avg_width));
+	if (!PQgetisnull(res, tup_num, i_n_distinct))
+		appendNamedArgument(out, fout, "n_distinct", "real",
+							PQgetvalue(res, tup_num, i_n_distinct));
+	if (!PQgetisnull(res, tup_num, i_most_common_vals))
+		appendNamedArgument(out, fout, "most_common_vals", "text",
+							PQgetvalue(res, tup_num, i_most_common_vals));
+	if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+		appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_freqs));
+	if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+		appendNamedArgument(out, fout, "histogram_bounds", "text",
+							PQgetvalue(res, tup_num, i_histogram_bounds));
+	if (!PQgetisnull(res, tup_num, i_correlation))
+		appendNamedArgument(out, fout, "correlation", "real",
+							PQgetvalue(res, tup_num, i_correlation));
+	if (!PQgetisnull(res, tup_num, i_most_common_elems))
+		appendNamedArgument(out, fout, "most_common_elems", "text",
+							PQgetvalue(res, tup_num, i_most_common_elems));
+	if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+		appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+	if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+		appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+							PQgetvalue(res, tup_num, i_elem_count_histogram));
+	if (fout->remoteVersion >= 170000)
+	{
+		if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+			appendNamedArgument(out, fout, "range_length_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_length_histogram));
+		if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+			appendNamedArgument(out, fout, "range_empty_frac", "real",
+								PQgetvalue(res, tup_num, i_range_empty_frac));
+		if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+			appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_bounds_histogram));
+	}
+	appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
  *
  * Generate the SQL statements needed to restore a relation's statistics.
  */
@@ -10564,64 +10879,21 @@ static char *
 printRelationStats(Archive *fout, const void *userArg)
 {
 	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
-	const DumpableObject *dobj = &rsinfo->dobj;
+	const DumpableObject *dobj;
+	const char *relschema;
+	const char *relname;
+
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
 
-	PQExpBufferData query;
 	PQExpBufferData out;
 
-	PGresult   *res;
-
-	static bool first_query = true;
-	static int	i_attname;
-	static int	i_inherited;
-	static int	i_null_frac;
-	static int	i_avg_width;
-	static int	i_n_distinct;
-	static int	i_most_common_vals;
-	static int	i_most_common_freqs;
-	static int	i_histogram_bounds;
-	static int	i_correlation;
-	static int	i_most_common_elems;
-	static int	i_most_common_elem_freqs;
-	static int	i_elem_count_histogram;
-	static int	i_range_length_histogram;
-	static int	i_range_empty_frac;
-	static int	i_range_bounds_histogram;
-
-	initPQExpBuffer(&query);
-
-	if (first_query)
-	{
-		appendPQExpBufferStr(&query,
-							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
-							 "SELECT s.attname, s.inherited, "
-							 "s.null_frac, s.avg_width, s.n_distinct, "
-							 "s.most_common_vals, s.most_common_freqs, "
-							 "s.histogram_bounds, s.correlation, "
-							 "s.most_common_elems, s.most_common_elem_freqs, "
-							 "s.elem_count_histogram, ");
-
-		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(&query,
-								 "s.range_length_histogram, "
-								 "s.range_empty_frac, "
-								 "s.range_bounds_histogram ");
-		else
-			appendPQExpBufferStr(&query,
-								 "NULL AS range_length_histogram,"
-								 "NULL AS range_empty_frac,"
-								 "NULL AS range_bounds_histogram ");
-
-		appendPQExpBufferStr(&query,
-							 "FROM pg_catalog.pg_stats s "
-							 "WHERE s.schemaname = $1 "
-							 "AND s.tablename = $2 "
-							 "ORDER BY s.attname, s.inherited");
-
-		ExecuteSqlStatement(fout, query.data);
-
-		resetPQExpBuffer(&query);
-	}
+	Assert(rsinfo != NULL);
+	dobj = &rsinfo->dobj;
+	Assert(dobj != NULL);
+	relschema = dobj->namespace->dobj.name;
+	Assert(relschema != NULL);
+	relname = dobj->name;
+	Assert(relname != NULL);
 
 	initPQExpBuffer(&out);
 
@@ -10642,132 +10914,72 @@ printRelationStats(Archive *fout, const void *userArg)
 	appendPQExpBufferStr(&out, "\n);\n");
 
 
-	/* fetch attribute stats */
-	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(&query, ", ");
-	appendStringLiteralAH(&query, dobj->name, fout);
-	appendPQExpBufferStr(&query, ")");
+	AH->txnCount++;
 
-	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+	if (attrstats.state == STATSBUF_UNINITIALIZED)
+		initAttributeStats(fout);
 
-	if (first_query)
+	/*
+	 * Because the query returns rows in the same order as the relations
+	 * requested, and because every relation gets at least one row in the
+	 * result set, the first row for this relation must correspond either to
+	 * the current row of this result set (if one exists) or the first row of
+	 * the next result set (if this one is already consumed).
+	 */
+	if (attrstats.state != STATSBUF_ACTIVE)
+		pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+				 rsinfo->dobj.namespace->dobj.name,
+				 rsinfo->dobj.name);
+
+	/*
+	 * If the current result set has been fully consumed, then the row(s) we
+	 * need (if any) would be found in the next one. This will update
+	 * attrstats.res and attrstats.idx.
+	 */
+	if (PQntuples(attrstats.res) <= attrstats.idx)
+		fetchNextAttributeStats(fout);
+
+	while (true)
 	{
-		i_attname = PQfnumber(res, "attname");
-		i_inherited = PQfnumber(res, "inherited");
-		i_null_frac = PQfnumber(res, "null_frac");
-		i_avg_width = PQfnumber(res, "avg_width");
-		i_n_distinct = PQfnumber(res, "n_distinct");
-		i_most_common_vals = PQfnumber(res, "most_common_vals");
-		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-		i_correlation = PQfnumber(res, "correlation");
-		i_most_common_elems = PQfnumber(res, "most_common_elems");
-		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
-		first_query = false;
-	}
-
-	/* restore attribute stats */
-	for (int rownum = 0; rownum < PQntuples(res); rownum++)
-	{
-		const char *attname;
-
-		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
-						  fout->remoteVersion);
-		appendPQExpBufferStr(&out, "\t'schemaname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(&out, ",\n\t'relname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
-		if (PQgetisnull(res, rownum, i_attname))
-			pg_fatal("attname cannot be NULL");
-		attname = PQgetvalue(res, rownum, i_attname);
+		int			i_schemaname;
+		int			i_tablename;
+		char	   *schemaname;
+		char	   *tablename;	/* misnomer, following pg_stats naming */
 
 		/*
-		 * Indexes look up attname in indAttNames to derive attnum, all others
-		 * use attname directly.  We must specify attnum for indexes, since
-		 * their attnames are not necessarily stable across dump/reload.
+		 * If we hit the end of the result set, then there are no more records
+		 * for this relation, so we should stop, but first get the next result
+		 * set for the next batch of relations.
 		 */
-		if (rsinfo->nindAttNames == 0)
+		if (PQntuples(attrstats.res) <= attrstats.idx)
 		{
-			appendPQExpBuffer(&out, ",\n\t'attname', ");
-			appendStringLiteralAH(&out, attname, fout);
-		}
-		else
-		{
-			bool		found = false;
-
-			for (int i = 0; i < rsinfo->nindAttNames; i++)
-			{
-				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
-				{
-					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
-									  i + 1);
-					found = true;
-					break;
-				}
-			}
-
-			if (!found)
-				pg_fatal("could not find index attname \"%s\"", attname);
+			fetchNextAttributeStats(fout);
+			break;
 		}
 
-		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(&out, fout, "inherited", "boolean",
-								PQgetvalue(res, rownum, i_inherited));
-		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(&out, fout, "null_frac", "real",
-								PQgetvalue(res, rownum, i_null_frac));
-		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(&out, fout, "avg_width", "integer",
-								PQgetvalue(res, rownum, i_avg_width));
-		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(&out, fout, "n_distinct", "real",
-								PQgetvalue(res, rownum, i_n_distinct));
-		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(&out, fout, "most_common_vals", "text",
-								PQgetvalue(res, rownum, i_most_common_vals));
-		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_freqs));
-		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(&out, fout, "histogram_bounds", "text",
-								PQgetvalue(res, rownum, i_histogram_bounds));
-		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(&out, fout, "correlation", "real",
-								PQgetvalue(res, rownum, i_correlation));
-		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(&out, fout, "most_common_elems", "text",
-								PQgetvalue(res, rownum, i_most_common_elems));
-		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_elem_freqs));
-		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
-								PQgetvalue(res, rownum, i_elem_count_histogram));
-		if (fout->remoteVersion >= 170000)
-		{
-			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(&out, fout, "range_length_histogram", "text",
-									PQgetvalue(res, rownum, i_range_length_histogram));
-			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(&out, fout, "range_empty_frac", "real",
-									PQgetvalue(res, rownum, i_range_empty_frac));
-			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
-									PQgetvalue(res, rownum, i_range_bounds_histogram));
-		}
-		appendPQExpBufferStr(&out, "\n);\n");
+		i_schemaname = PQfnumber(attrstats.res, "schemaname");
+		Assert(i_schemaname >= 0);
+		i_tablename = PQfnumber(attrstats.res, "tablename");
+		Assert(i_tablename >= 0);
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+			pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+			pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+		schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+		tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+		/* stop if current stat row isn't for this relation */
+		if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+			break;
+
+		appendAttributeStats(fout, &out, rsinfo);
+		AH->txnCount++;
+		attrstats.idx++;
 	}
 
-	PQclear(res);
-
-	termPQExpBuffer(&query);
 	return out.data;
 }
 
-- 
2.49.0



  [text/x-patch] v11-0002-Introduce-CreateStmtPtr.patch (17.2K, ../../CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com/6-v11-0002-Introduce-CreateStmtPtr.patch)
  download | inline diff:
From c71426ee2068d37237b79a65d1a0764b7cd3d60f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v11 2/4] Introduce CreateStmtPtr.

CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.

Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
 src/bin/pg_dump/pg_backup.h          |   2 +
 src/bin/pg_dump/pg_backup_archiver.c |  22 ++-
 src/bin/pg_dump/pg_backup_archiver.h |   7 +
 src/bin/pg_dump/pg_dump.c            | 229 +++++++++++++++------------
 4 files changed, 158 insertions(+), 102 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 658986de6f8..fdcccd64a70 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -289,6 +289,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
 
 typedef void (*SetupWorkerPtrType) (Archive *AH);
 
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
 /*
  * Main archiver interface.
  */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 1d131e5a57d..1b4c62fd7d7 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1265,6 +1265,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
 	newToc->dataDumper = opts->dumpFn;
 	newToc->dataDumperArg = opts->dumpArg;
 	newToc->hadDumper = opts->dumpFn ? true : false;
+	newToc->createDumper = opts->createFn;
+	newToc->createDumperArg = opts->createArg;
+	newToc->hadCreateDumper = opts->createFn ? true : false;
 
 	newToc->formatData = NULL;
 	newToc->dataLength = 0;
@@ -2621,7 +2624,17 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->tag);
 		WriteStr(AH, te->desc);
 		WriteInt(AH, te->section);
-		WriteStr(AH, te->defn);
+
+		if (te->hadCreateDumper)
+		{
+			char	   *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+			WriteStr(AH, defn);
+			pg_free(defn);
+		}
+		else
+			WriteStr(AH, te->defn);
+
 		WriteStr(AH, te->dropStmt);
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
@@ -3877,6 +3890,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
 	{
 		IssueACLPerBlob(AH, te);
 	}
+	else if (te->hadCreateDumper)
+	{
+		char	   *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+		ahwrite(ptr, 1, strlen(ptr), AH);
+		pg_free(ptr);
+	}
 	else if (te->defn && strlen(te->defn) > 0)
 	{
 		ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
 	const void *dataDumperArg;	/* Arg for above routine */
 	void	   *formatData;		/* TOC Entry data specific to file format */
 
+	CreateStmtPtr createDumper; /* Routine for create statement creation */
+	const void *createDumperArg;	/* arg for the above routine */
+	bool		hadCreateDumper;	/* Archiver was passed a create statement
+									 * routine */
+
 	/* working state while dumping/restoring */
 	pgoff_t		dataLength;		/* item's data size; 0 if none or unknown */
 	int			reqs;			/* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
 	int			nDeps;
 	DataDumperPtr dumpFn;
 	const void *dumpArg;
+	CreateStmtPtr createFn;
+	const void *createArg;
 } ArchiveOpts;
 #define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
 /* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 211cf10dbd6..b7571ea15eb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10556,42 +10556,44 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
 }
 
 /*
- * dumpRelationStats --
+ * printDumpRelationStats --
  *
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
  */
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
 {
+	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
 	const DumpableObject *dobj = &rsinfo->dobj;
+
+	PQExpBufferData query;
+	PQExpBufferData out;
+
 	PGresult   *res;
-	PQExpBuffer query;
-	PQExpBuffer out;
-	int			i_attname;
-	int			i_inherited;
-	int			i_null_frac;
-	int			i_avg_width;
-	int			i_n_distinct;
-	int			i_most_common_vals;
-	int			i_most_common_freqs;
-	int			i_histogram_bounds;
-	int			i_correlation;
-	int			i_most_common_elems;
-	int			i_most_common_elem_freqs;
-	int			i_elem_count_histogram;
-	int			i_range_length_histogram;
-	int			i_range_empty_frac;
-	int			i_range_bounds_histogram;
 
-	/* nothing to do if we are not dumping statistics */
-	if (!fout->dopt->dumpStatistics)
-		return;
+	static bool first_query = true;
+	static int	i_attname;
+	static int	i_inherited;
+	static int	i_null_frac;
+	static int	i_avg_width;
+	static int	i_n_distinct;
+	static int	i_most_common_vals;
+	static int	i_most_common_freqs;
+	static int	i_histogram_bounds;
+	static int	i_correlation;
+	static int	i_most_common_elems;
+	static int	i_most_common_elem_freqs;
+	static int	i_elem_count_histogram;
+	static int	i_range_length_histogram;
+	static int	i_range_empty_frac;
+	static int	i_range_bounds_histogram;
 
-	query = createPQExpBuffer();
-	if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
+	initPQExpBuffer(&query);
+
+	if (first_query)
 	{
-		appendPQExpBufferStr(query,
-							 "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+		appendPQExpBufferStr(&query,
+							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
 							 "SELECT s.attname, s.inherited, "
 							 "s.null_frac, s.avg_width, s.n_distinct, "
 							 "s.most_common_vals, s.most_common_freqs, "
@@ -10600,88 +10602,87 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							 "s.elem_count_histogram, ");
 
 		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "s.range_length_histogram, "
 								 "s.range_empty_frac, "
 								 "s.range_bounds_histogram ");
 		else
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "NULL AS range_length_histogram,"
 								 "NULL AS range_empty_frac,"
 								 "NULL AS range_bounds_histogram ");
 
-		appendPQExpBufferStr(query,
+		appendPQExpBufferStr(&query,
 							 "FROM pg_catalog.pg_stats s "
 							 "WHERE s.schemaname = $1 "
 							 "AND s.tablename = $2 "
 							 "ORDER BY s.attname, s.inherited");
 
-		ExecuteSqlStatement(fout, query->data);
+		ExecuteSqlStatement(fout, query.data);
 
-		fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
-		resetPQExpBuffer(query);
+		resetPQExpBuffer(&query);
 	}
 
-	out = createPQExpBuffer();
+	initPQExpBuffer(&out);
 
 	/* restore relation stats */
-	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
-					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'schemaname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBufferStr(out, "\t'relname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
-					  rsinfo->relallvisible);
+	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(");
+	appendPQExpBuffer(&out, "\n\t'version', '%u'::integer", fout->remoteVersion);
+	appendPQExpBufferStr(&out, ",\n\t'schemaname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(&out, ",\n\t'relname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+	appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages);
+	appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples);
+	appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible);
 
 	if (fout->remoteVersion >= 180000)
-		appendPQExpBuffer(out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+		appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
 
-	appendPQExpBufferStr(out, "\n);\n");
+	appendPQExpBufferStr(&out, "\n);\n");
 
 
 	/* fetch attribute stats */
-	appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(query, ", ");
-	appendStringLiteralAH(query, dobj->name, fout);
-	appendPQExpBufferStr(query, ");");
+	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+	appendPQExpBufferStr(&query, ", ");
+	appendStringLiteralAH(&query, dobj->name, fout);
+	appendPQExpBufferStr(&query, ")");
 
-	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
 
-	i_attname = PQfnumber(res, "attname");
-	i_inherited = PQfnumber(res, "inherited");
-	i_null_frac = PQfnumber(res, "null_frac");
-	i_avg_width = PQfnumber(res, "avg_width");
-	i_n_distinct = PQfnumber(res, "n_distinct");
-	i_most_common_vals = PQfnumber(res, "most_common_vals");
-	i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-	i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-	i_correlation = PQfnumber(res, "correlation");
-	i_most_common_elems = PQfnumber(res, "most_common_elems");
-	i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-	i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-	i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-	i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-	i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+	if (first_query)
+	{
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		first_query = false;
+	}
 
 	/* restore attribute stats */
 	for (int rownum = 0; rownum < PQntuples(res); rownum++)
 	{
 		const char *attname;
 
-		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'schemaname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(out, ",\n\t'relname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+		appendPQExpBufferStr(&out, "\t'schemaname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(&out, ",\n\t'relname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10694,8 +10695,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 */
 		if (rsinfo->nindAttNames == 0)
 		{
-			appendPQExpBuffer(out, ",\n\t'attname', ");
-			appendStringLiteralAH(out, attname, fout);
+			appendPQExpBuffer(&out, ",\n\t'attname', ");
+			appendStringLiteralAH(&out, attname, fout);
 		}
 		else
 		{
@@ -10705,7 +10706,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 			{
 				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
 				{
-					appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
 									  i + 1);
 					found = true;
 					break;
@@ -10717,66 +10718,92 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		}
 
 		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(out, fout, "inherited", "boolean",
+			appendNamedArgument(&out, fout, "inherited", "boolean",
 								PQgetvalue(res, rownum, i_inherited));
 		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(out, fout, "null_frac", "real",
+			appendNamedArgument(&out, fout, "null_frac", "real",
 								PQgetvalue(res, rownum, i_null_frac));
 		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(out, fout, "avg_width", "integer",
+			appendNamedArgument(&out, fout, "avg_width", "integer",
 								PQgetvalue(res, rownum, i_avg_width));
 		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(out, fout, "n_distinct", "real",
+			appendNamedArgument(&out, fout, "n_distinct", "real",
 								PQgetvalue(res, rownum, i_n_distinct));
 		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(out, fout, "most_common_vals", "text",
+			appendNamedArgument(&out, fout, "most_common_vals", "text",
 								PQgetvalue(res, rownum, i_most_common_vals));
 		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_freqs));
 		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(out, fout, "histogram_bounds", "text",
+			appendNamedArgument(&out, fout, "histogram_bounds", "text",
 								PQgetvalue(res, rownum, i_histogram_bounds));
 		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(out, fout, "correlation", "real",
+			appendNamedArgument(&out, fout, "correlation", "real",
 								PQgetvalue(res, rownum, i_correlation));
 		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(out, fout, "most_common_elems", "text",
+			appendNamedArgument(&out, fout, "most_common_elems", "text",
 								PQgetvalue(res, rownum, i_most_common_elems));
 		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_elem_freqs));
 		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
 								PQgetvalue(res, rownum, i_elem_count_histogram));
 		if (fout->remoteVersion >= 170000)
 		{
 			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(out, fout, "range_length_histogram", "text",
+				appendNamedArgument(&out, fout, "range_length_histogram", "text",
 									PQgetvalue(res, rownum, i_range_length_histogram));
 			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(out, fout, "range_empty_frac", "real",
+				appendNamedArgument(&out, fout, "range_empty_frac", "real",
 									PQgetvalue(res, rownum, i_range_empty_frac));
 			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
 									PQgetvalue(res, rownum, i_range_bounds_histogram));
 		}
-		appendPQExpBufferStr(out, "\n);\n");
+		appendPQExpBufferStr(&out, "\n);\n");
 	}
 
 	PQclear(res);
 
+	termPQExpBuffer(&query);
+	return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+	const DumpableObject *dobj = &rsinfo->dobj;
+
+	DumpId	   *deps = NULL;
+	int			ndeps = 0;
+
+	/* nothing to do if we are not dumping statistics */
+	if (!fout->dopt->dumpStatistics)
+		return;
+
+	/* dependent on the relation definition, if doing schema */
+	if (fout->dopt->dumpSchema)
+	{
+		deps = dobj->dependencies;
+		ndeps = dobj->nDeps;
+	}
+
 	ArchiveEntry(fout, nilCatalogId, createDumpId(),
 				 ARCHIVE_OPTS(.tag = dobj->name,
 							  .namespace = dobj->namespace->dobj.name,
 							  .description = "STATISTICS DATA",
 							  .section = rsinfo->section,
-							  .createStmt = out->data,
-							  .deps = dobj->dependencies,
-							  .nDeps = dobj->nDeps));
-
-	destroyPQExpBuffer(out);
-	destroyPQExpBuffer(query);
+							  .createFn = printRelationStats,
+							  .createArg = rsinfo,
+							  .deps = deps,
+							  .nDeps = ndeps));
 }
 
 /*
-- 
2.49.0



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-29 05:29                                 ` Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-29 05:29 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, 2025-03-28 at 21:11 -0400, Corey Huinker wrote:
> A rebase and a reordering of the commits to put the really-really-
> must-have relallfrozen ahead of the really-must-have stats batching
> and both of them head of the error->warning step-downs.

v11-0001 has a couple issues:

The first is that i_relallfrozen is undefined in versions earlier than
18. That's trivial to fix, we just add "0 AS relallfrozen," in the
earlier versions, but still refrain from outputting it.

The second is that the pg_upgrade test (when run with
olddump/oldinstall) compares the before and after dumps, and if the
"before" version is 17, then it will not have the relallfrozen argument
to pg_restore_relation_stats. We might need a filtering step in
adjust_new_dumpfile?

Attached new v11j-0001

Regards,
	Jeff Davis



Attachments:

  [text/x-patch] v11j-0001-Add-relallfrozen-to-pg_dump-statistics.patch (7.7K, ../../[email protected]/2-v11j-0001-Add-relallfrozen-to-pg_dump-statistics.patch)
  download | inline diff:
From 154b8b5c10ec330c26ccd9006c434a7db1feef04 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 15 Mar 2025 17:34:30 -0400
Subject: [PATCH v11j] Add relallfrozen to pg_dump statistics.

Author: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=desCuf3dVHasADvdUVRmb-5gO0mhMO5u9nzgv6i7U86Q@mail.gmail.com
---
 src/bin/pg_dump/pg_dump.c                     | 42 +++++++++++++++----
 src/bin/pg_dump/pg_dump.h                     |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl              |  3 +-
 .../perl/PostgreSQL/Test/AdjustUpgrade.pm     |  5 +++
 4 files changed, 43 insertions(+), 8 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 84a78625820..4ca34be230c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6874,7 +6874,8 @@ getFuncs(Archive *fout)
  */
 static RelStatsInfo *
 getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
-					  char *reltuples, int32 relallvisible, char relkind,
+					  char *reltuples, int32 relallvisible,
+					  int32 relallfrozen, char relkind,
 					  char **indAttNames, int nindAttNames)
 {
 	if (!fout->dopt->dumpStatistics)
@@ -6903,6 +6904,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
 		info->relpages = relpages;
 		info->reltuples = pstrdup(reltuples);
 		info->relallvisible = relallvisible;
+		info->relallfrozen = relallfrozen;
 		info->relkind = relkind;
 		info->indAttNames = indAttNames;
 		info->nindAttNames = nindAttNames;
@@ -6967,6 +6969,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relpages;
 	int			i_reltuples;
 	int			i_relallvisible;
+	int			i_relallfrozen;
 	int			i_toastpages;
 	int			i_owning_tab;
 	int			i_owning_col;
@@ -7017,8 +7020,15 @@ getTables(Archive *fout, int *numTables)
 						 "c.relowner, "
 						 "c.relchecks, "
 						 "c.relhasindex, c.relhasrules, c.relpages, "
-						 "c.reltuples, c.relallvisible, c.relhastriggers, "
-						 "c.relpersistence, "
+						 "c.reltuples, c.relallvisible, ");
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBufferStr(query, "c.relallfrozen, ");
+	else
+		appendPQExpBufferStr(query, "0 AS relallfrozen, ");
+
+	appendPQExpBufferStr(query,
+						 "c.relhastriggers, c.relpersistence, "
 						 "c.reloftype, "
 						 "c.relacl, "
 						 "acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
@@ -7183,6 +7193,7 @@ getTables(Archive *fout, int *numTables)
 	i_relpages = PQfnumber(res, "relpages");
 	i_reltuples = PQfnumber(res, "reltuples");
 	i_relallvisible = PQfnumber(res, "relallvisible");
+	i_relallfrozen = PQfnumber(res, "relallfrozen");
 	i_toastpages = PQfnumber(res, "toastpages");
 	i_owning_tab = PQfnumber(res, "owning_tab");
 	i_owning_col = PQfnumber(res, "owning_col");
@@ -7230,6 +7241,7 @@ getTables(Archive *fout, int *numTables)
 	for (i = 0; i < ntups; i++)
 	{
 		int32		relallvisible = atoi(PQgetvalue(res, i, i_relallvisible));
+		int32		relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen));
 
 		tblinfo[i].dobj.objType = DO_TABLE;
 		tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
@@ -7336,7 +7348,7 @@ getTables(Archive *fout, int *numTables)
 			stats = getRelationStatistics(fout, &tblinfo[i].dobj,
 										  tblinfo[i].relpages,
 										  PQgetvalue(res, i, i_reltuples),
-										  relallvisible,
+										  relallvisible, relallfrozen,
 										  tblinfo[i].relkind, NULL, 0);
 			if (tblinfo[i].relkind == RELKIND_MATVIEW)
 				tblinfo[i].stats = stats;
@@ -7609,6 +7621,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_relpages,
 				i_reltuples,
 				i_relallvisible,
+				i_relallfrozen,
 				i_parentidx,
 				i_indexdef,
 				i_indnkeyatts,
@@ -7663,7 +7676,14 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	appendPQExpBufferStr(query,
 						 "SELECT t.tableoid, t.oid, i.indrelid, "
 						 "t.relname AS indexname, "
-						 "t.relpages, t.reltuples, t.relallvisible, "
+						 "t.relpages, t.reltuples, t.relallvisible, ");
+
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBufferStr(query, "t.relallfrozen, ");
+	else
+		appendPQExpBufferStr(query, "0 AS relallfrozen, ");
+
+	appendPQExpBufferStr(query,
 						 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
 						 "i.indkey, i.indisclustered, "
 						 "c.contype, c.conname, "
@@ -7779,6 +7799,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	i_relpages = PQfnumber(res, "relpages");
 	i_reltuples = PQfnumber(res, "reltuples");
 	i_relallvisible = PQfnumber(res, "relallvisible");
+	i_relallfrozen = PQfnumber(res, "relallfrozen");
 	i_parentidx = PQfnumber(res, "parentidx");
 	i_indexdef = PQfnumber(res, "indexdef");
 	i_indnkeyatts = PQfnumber(res, "indnkeyatts");
@@ -7850,6 +7871,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			RelStatsInfo *relstats;
 			int32		relpages = atoi(PQgetvalue(res, j, i_relpages));
 			int32		relallvisible = atoi(PQgetvalue(res, j, i_relallvisible));
+			int32		relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen));
 
 			indxinfo[j].dobj.objType = DO_INDEX;
 			indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
@@ -7892,7 +7914,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 
 			relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
 											 PQgetvalue(res, j, i_reltuples),
-											 relallvisible, indexkind,
+											 relallvisible, relallfrozen, indexkind,
 											 indAttNames, nindAttNames);
 
 			contype = *(PQgetvalue(res, j, i_contype));
@@ -10618,9 +10640,15 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 	appendPQExpBufferStr(out, ",\n");
 	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
 	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
 					  rsinfo->relallvisible);
 
+	if (fout->remoteVersion >= 180000)
+		appendPQExpBuffer(out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+
+	appendPQExpBufferStr(out, "\n);\n");
+
+
 	/* fetch attribute stats */
 	appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
 	appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 70f7a369e4a..e6f0f86a459 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -442,6 +442,7 @@ typedef struct _relStatsInfo
 	int32		relpages;
 	char	   *reltuples;
 	int32		relallvisible;
+	int32		relallfrozen;
 	char		relkind;		/* 'r', 'm', 'i', etc */
 
 	/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 51ebf8ad13c..576326daec7 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4771,7 +4771,8 @@ my %tests = (
 			'relname',\s'dup_test_post_data_ix',\s+
 			'relpages',\s'\d+'::integer,\s+
 			'reltuples',\s'\d+'::real,\s+
-			'relallvisible',\s'\d+'::integer\s+
+			'relallvisible',\s'\d+'::integer,\s+
+			'relallfrozen',\s'\d+'::integer\s+
 			\);\s+
 			\QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+
 			'version',\s'\d+'::integer,\s+
diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
index 81a8f44aa9f..07550295a82 100644
--- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
+++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
@@ -648,6 +648,11 @@ sub adjust_new_dumpfile
 	$dump =~ s {\n(\s+'version',) '\d+'::integer,$}
 		{$1 '000000'::integer,}mg;
 
+	if ($old_version < 18)
+	{
+		$dump =~ s {,\n(\s+'relallfrozen',) '\d+'::integer$}{}mg;
+	}
+
 	# pre-v16 dumps do not know about XMLSERIALIZE(NO INDENT).
 	if ($old_version < 16)
 	{
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-03-29 05:44                                   ` Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-29 05:44 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> The first is that i_relallfrozen is undefined in versions earlier than
> 18. That's trivial to fix, we just add "0 AS relallfrozen," in the
> earlier versions, but still refrain from outputting it.
>

Ok, so long as we refrain from outputting it, I'm cool with whatever we
store internally.



> The second is that the pg_upgrade test (when run with
> olddump/oldinstall) compares the before and after dumps, and if the
> "before" version is 17, then it will not have the relallfrozen argument
> to pg_restore_relation_stats. We might need a filtering step in
> adjust_new_dumpfile?
>

That sounds trickier. Do we already have filtering steps that are sensitive
to the "before" version dump?


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-31 15:11                                     ` Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-31 15:11 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> The second is that the pg_upgrade test (when run with
>> olddump/oldinstall) compares the before and after dumps, and if the
>> "before" version is 17, then it will not have the relallfrozen argument
>> to pg_restore_relation_stats. We might need a filtering step in
>> adjust_new_dumpfile?
>>
>
> That sounds trickier.
>

Narrator: It was not trickier.

In light of v11-0001 being committed as 4694aedf63bf, I've rebased the
remaining patches.


Attachments:

  [text/x-patch] v12-0001-Introduce-CreateStmtPtr.patch (17.3K, ../../CADkLM=domd5+CvjKMHGbOfvSuY6J8G-x+9M2D6Ss2HamYefE9w@mail.gmail.com/3-v12-0001-Introduce-CreateStmtPtr.patch)
  download | inline diff:
From 607984bdcc91fa31fb7a12e9b24fb8704aa14975 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 01:06:19 -0400
Subject: [PATCH v12 1/3] Introduce CreateStmtPtr.

CreateStmtPtr is a function pointer that can replace the createStmt/defn
parameter. This is useful in situations where the amount of text
generated for a definition is so large that it is undesirable to hold
many such objects in memory at the same time.

Using functions of this type, the text created is then immediately
written out to the appropriate file for the given dump format.
---
 src/bin/pg_dump/pg_backup.h          |   2 +
 src/bin/pg_dump/pg_backup_archiver.c |  22 ++-
 src/bin/pg_dump/pg_backup_archiver.h |   7 +
 src/bin/pg_dump/pg_dump.c            | 229 +++++++++++++++------------
 4 files changed, 158 insertions(+), 102 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 658986de6f8..fdcccd64a70 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -289,6 +289,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg);
 
 typedef void (*SetupWorkerPtrType) (Archive *AH);
 
+typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg);
+
 /*
  * Main archiver interface.
  */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 1d131e5a57d..1b4c62fd7d7 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1265,6 +1265,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
 	newToc->dataDumper = opts->dumpFn;
 	newToc->dataDumperArg = opts->dumpArg;
 	newToc->hadDumper = opts->dumpFn ? true : false;
+	newToc->createDumper = opts->createFn;
+	newToc->createDumperArg = opts->createArg;
+	newToc->hadCreateDumper = opts->createFn ? true : false;
 
 	newToc->formatData = NULL;
 	newToc->dataLength = 0;
@@ -2621,7 +2624,17 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->tag);
 		WriteStr(AH, te->desc);
 		WriteInt(AH, te->section);
-		WriteStr(AH, te->defn);
+
+		if (te->hadCreateDumper)
+		{
+			char	   *defn = te->createDumper((Archive *) AH, te->createDumperArg);
+
+			WriteStr(AH, defn);
+			pg_free(defn);
+		}
+		else
+			WriteStr(AH, te->defn);
+
 		WriteStr(AH, te->dropStmt);
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
@@ -3877,6 +3890,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
 	{
 		IssueACLPerBlob(AH, te);
 	}
+	else if (te->hadCreateDumper)
+	{
+		char	   *ptr = te->createDumper((Archive *) AH, te->createDumperArg);
+
+		ahwrite(ptr, 1, strlen(ptr), AH);
+		pg_free(ptr);
+	}
 	else if (te->defn && strlen(te->defn) > 0)
 	{
 		ahprintf(AH, "%s\n\n", te->defn);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index a2064f471ed..e68db633995 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -368,6 +368,11 @@ struct _tocEntry
 	const void *dataDumperArg;	/* Arg for above routine */
 	void	   *formatData;		/* TOC Entry data specific to file format */
 
+	CreateStmtPtr createDumper; /* Routine for create statement creation */
+	const void *createDumperArg;	/* arg for the above routine */
+	bool		hadCreateDumper;	/* Archiver was passed a create statement
+									 * routine */
+
 	/* working state while dumping/restoring */
 	pgoff_t		dataLength;		/* item's data size; 0 if none or unknown */
 	int			reqs;			/* do we need schema and/or data of object
@@ -407,6 +412,8 @@ typedef struct _archiveOpts
 	int			nDeps;
 	DataDumperPtr dumpFn;
 	const void *dumpArg;
+	CreateStmtPtr createFn;
+	const void *createArg;
 } ArchiveOpts;
 #define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__}
 /* Called to add a TOC entry */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ca34be230c..cc195d6cd9e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10560,42 +10560,44 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
 }
 
 /*
- * dumpRelationStats --
+ * printDumpRelationStats --
  *
- * Dump command to import stats into the relation on the new database.
+ * Generate the SQL statements needed to restore a relation's statistics.
  */
-static void
-dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+static char *
+printRelationStats(Archive *fout, const void *userArg)
 {
+	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
 	const DumpableObject *dobj = &rsinfo->dobj;
+
+	PQExpBufferData query;
+	PQExpBufferData out;
+
 	PGresult   *res;
-	PQExpBuffer query;
-	PQExpBuffer out;
-	int			i_attname;
-	int			i_inherited;
-	int			i_null_frac;
-	int			i_avg_width;
-	int			i_n_distinct;
-	int			i_most_common_vals;
-	int			i_most_common_freqs;
-	int			i_histogram_bounds;
-	int			i_correlation;
-	int			i_most_common_elems;
-	int			i_most_common_elem_freqs;
-	int			i_elem_count_histogram;
-	int			i_range_length_histogram;
-	int			i_range_empty_frac;
-	int			i_range_bounds_histogram;
 
-	/* nothing to do if we are not dumping statistics */
-	if (!fout->dopt->dumpStatistics)
-		return;
+	static bool first_query = true;
+	static int	i_attname;
+	static int	i_inherited;
+	static int	i_null_frac;
+	static int	i_avg_width;
+	static int	i_n_distinct;
+	static int	i_most_common_vals;
+	static int	i_most_common_freqs;
+	static int	i_histogram_bounds;
+	static int	i_correlation;
+	static int	i_most_common_elems;
+	static int	i_most_common_elem_freqs;
+	static int	i_elem_count_histogram;
+	static int	i_range_length_histogram;
+	static int	i_range_empty_frac;
+	static int	i_range_bounds_histogram;
 
-	query = createPQExpBuffer();
-	if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
+	initPQExpBuffer(&query);
+
+	if (first_query)
 	{
-		appendPQExpBufferStr(query,
-							 "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
+		appendPQExpBufferStr(&query,
+							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
 							 "SELECT s.attname, s.inherited, "
 							 "s.null_frac, s.avg_width, s.n_distinct, "
 							 "s.most_common_vals, s.most_common_freqs, "
@@ -10604,88 +10606,87 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 							 "s.elem_count_histogram, ");
 
 		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "s.range_length_histogram, "
 								 "s.range_empty_frac, "
 								 "s.range_bounds_histogram ");
 		else
-			appendPQExpBufferStr(query,
+			appendPQExpBufferStr(&query,
 								 "NULL AS range_length_histogram,"
 								 "NULL AS range_empty_frac,"
 								 "NULL AS range_bounds_histogram ");
 
-		appendPQExpBufferStr(query,
+		appendPQExpBufferStr(&query,
 							 "FROM pg_catalog.pg_stats s "
 							 "WHERE s.schemaname = $1 "
 							 "AND s.tablename = $2 "
 							 "ORDER BY s.attname, s.inherited");
 
-		ExecuteSqlStatement(fout, query->data);
+		ExecuteSqlStatement(fout, query.data);
 
-		fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true;
-		resetPQExpBuffer(query);
+		resetPQExpBuffer(&query);
 	}
 
-	out = createPQExpBuffer();
+	initPQExpBuffer(&out);
 
 	/* restore relation stats */
-	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
-					  fout->remoteVersion);
-	appendPQExpBufferStr(out, "\t'schemaname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBufferStr(out, "\t'relname', ");
-	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
-	appendPQExpBufferStr(out, ",\n");
-	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
-	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
-					  rsinfo->relallvisible);
+	appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(");
+	appendPQExpBuffer(&out, "\n\t'version', '%u'::integer", fout->remoteVersion);
+	appendPQExpBufferStr(&out, ",\n\t'schemaname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(&out, ",\n\t'relname', ");
+	appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
+	appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages);
+	appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples);
+	appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible);
 
 	if (fout->remoteVersion >= 180000)
-		appendPQExpBuffer(out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
+		appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen);
 
-	appendPQExpBufferStr(out, "\n);\n");
+	appendPQExpBufferStr(&out, "\n);\n");
 
 
 	/* fetch attribute stats */
-	appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(query, ", ");
-	appendStringLiteralAH(query, dobj->name, fout);
-	appendPQExpBufferStr(query, ");");
+	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
+	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
+	appendPQExpBufferStr(&query, ", ");
+	appendStringLiteralAH(&query, dobj->name, fout);
+	appendPQExpBufferStr(&query, ")");
 
-	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
 
-	i_attname = PQfnumber(res, "attname");
-	i_inherited = PQfnumber(res, "inherited");
-	i_null_frac = PQfnumber(res, "null_frac");
-	i_avg_width = PQfnumber(res, "avg_width");
-	i_n_distinct = PQfnumber(res, "n_distinct");
-	i_most_common_vals = PQfnumber(res, "most_common_vals");
-	i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-	i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-	i_correlation = PQfnumber(res, "correlation");
-	i_most_common_elems = PQfnumber(res, "most_common_elems");
-	i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-	i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-	i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-	i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-	i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+	if (first_query)
+	{
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		first_query = false;
+	}
 
 	/* restore attribute stats */
 	for (int rownum = 0; rownum < PQntuples(res); rownum++)
 	{
 		const char *attname;
 
-		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
 						  fout->remoteVersion);
-		appendPQExpBufferStr(out, "\t'schemaname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(out, ",\n\t'relname', ");
-		appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+		appendPQExpBufferStr(&out, "\t'schemaname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
+		appendPQExpBufferStr(&out, ",\n\t'relname', ");
+		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
 
 		if (PQgetisnull(res, rownum, i_attname))
 			pg_fatal("attname cannot be NULL");
@@ -10698,8 +10699,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		 */
 		if (rsinfo->nindAttNames == 0)
 		{
-			appendPQExpBuffer(out, ",\n\t'attname', ");
-			appendStringLiteralAH(out, attname, fout);
+			appendPQExpBuffer(&out, ",\n\t'attname', ");
+			appendStringLiteralAH(&out, attname, fout);
 		}
 		else
 		{
@@ -10709,7 +10710,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 			{
 				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
 				{
-					appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
 									  i + 1);
 					found = true;
 					break;
@@ -10721,66 +10722,92 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		}
 
 		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(out, fout, "inherited", "boolean",
+			appendNamedArgument(&out, fout, "inherited", "boolean",
 								PQgetvalue(res, rownum, i_inherited));
 		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(out, fout, "null_frac", "real",
+			appendNamedArgument(&out, fout, "null_frac", "real",
 								PQgetvalue(res, rownum, i_null_frac));
 		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(out, fout, "avg_width", "integer",
+			appendNamedArgument(&out, fout, "avg_width", "integer",
 								PQgetvalue(res, rownum, i_avg_width));
 		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(out, fout, "n_distinct", "real",
+			appendNamedArgument(&out, fout, "n_distinct", "real",
 								PQgetvalue(res, rownum, i_n_distinct));
 		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(out, fout, "most_common_vals", "text",
+			appendNamedArgument(&out, fout, "most_common_vals", "text",
 								PQgetvalue(res, rownum, i_most_common_vals));
 		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_freqs));
 		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(out, fout, "histogram_bounds", "text",
+			appendNamedArgument(&out, fout, "histogram_bounds", "text",
 								PQgetvalue(res, rownum, i_histogram_bounds));
 		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(out, fout, "correlation", "real",
+			appendNamedArgument(&out, fout, "correlation", "real",
 								PQgetvalue(res, rownum, i_correlation));
 		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(out, fout, "most_common_elems", "text",
+			appendNamedArgument(&out, fout, "most_common_elems", "text",
 								PQgetvalue(res, rownum, i_most_common_elems));
 		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
 								PQgetvalue(res, rownum, i_most_common_elem_freqs));
 		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
 								PQgetvalue(res, rownum, i_elem_count_histogram));
 		if (fout->remoteVersion >= 170000)
 		{
 			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(out, fout, "range_length_histogram", "text",
+				appendNamedArgument(&out, fout, "range_length_histogram", "text",
 									PQgetvalue(res, rownum, i_range_length_histogram));
 			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(out, fout, "range_empty_frac", "real",
+				appendNamedArgument(&out, fout, "range_empty_frac", "real",
 									PQgetvalue(res, rownum, i_range_empty_frac));
 			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
 									PQgetvalue(res, rownum, i_range_bounds_histogram));
 		}
-		appendPQExpBufferStr(out, "\n);\n");
+		appendPQExpBufferStr(&out, "\n);\n");
 	}
 
 	PQclear(res);
 
+	termPQExpBuffer(&query);
+	return out.data;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+	const DumpableObject *dobj = &rsinfo->dobj;
+
+	DumpId	   *deps = NULL;
+	int			ndeps = 0;
+
+	/* nothing to do if we are not dumping statistics */
+	if (!fout->dopt->dumpStatistics)
+		return;
+
+	/* dependent on the relation definition, if doing schema */
+	if (fout->dopt->dumpSchema)
+	{
+		deps = dobj->dependencies;
+		ndeps = dobj->nDeps;
+	}
+
 	ArchiveEntry(fout, nilCatalogId, createDumpId(),
 				 ARCHIVE_OPTS(.tag = dobj->name,
 							  .namespace = dobj->namespace->dobj.name,
 							  .description = "STATISTICS DATA",
 							  .section = rsinfo->section,
-							  .createStmt = out->data,
-							  .deps = dobj->dependencies,
-							  .nDeps = dobj->nDeps));
-
-	destroyPQExpBuffer(out);
-	destroyPQExpBuffer(query);
+							  .createFn = printRelationStats,
+							  .createArg = rsinfo,
+							  .deps = deps,
+							  .nDeps = ndeps));
 }
 
 /*

base-commit: e2809e3a1015697832ee4d37b75ba1cd0caac0f0
-- 
2.49.0



  [text/x-patch] v12-0002-Batching-getAttributeStats.patch (21.5K, ../../CADkLM=domd5+CvjKMHGbOfvSuY6J8G-x+9M2D6Ss2HamYefE9w@mail.gmail.com/4-v12-0002-Batching-getAttributeStats.patch)
  download | inline diff:
From 410171805037718c3adcae778bba56c485038e3f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 14 Mar 2025 03:54:26 -0400
Subject: [PATCH v12 2/3] Batching getAttributeStats().

The prepared statement getAttributeStats() is fairly heavyweight and
could greatly increase pg_dump/pg_upgrade runtime. To alleviate this,
create a result set buffer of all of the attribute stats fetched for a
batch of 100 relations that could potentially have stats.

The query ensures that the order of results exactly matches the needs of
the code walking the TOC to print the stats calls.
---
 src/bin/pg_dump/pg_dump.c | 554 ++++++++++++++++++++++++++------------
 1 file changed, 383 insertions(+), 171 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index cc195d6cd9e..26144371b1b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -143,6 +143,25 @@ typedef enum OidOptions
 	zeroAsNone = 4,
 } OidOptions;
 
+typedef enum StatsBufferState
+{
+	STATSBUF_UNINITIALIZED = 0,
+	STATSBUF_ACTIVE,
+	STATSBUF_EXHAUSTED
+}			StatsBufferState;
+
+typedef struct
+{
+	PGresult   *res;			/* results from most recent
+								 * getAttributeStats() */
+	int			idx;			/* first un-consumed row of results */
+	TocEntry   *te;				/* next TOC entry to search for statsitics
+								 * data */
+
+	StatsBufferState state;		/* current state of the buffer */
+}			AttributeStatsBuffer;
+
+
 /* global decls */
 static bool dosync = true;		/* Issue fsync() to make dump durable on disk. */
 
@@ -209,6 +228,18 @@ static int	nbinaryUpgradeClassOids = 0;
 static SequenceItem *sequences = NULL;
 static int	nsequences = 0;
 
+static AttributeStatsBuffer attrstats =
+{
+	NULL, 0, NULL, STATSBUF_UNINITIALIZED
+};
+
+/*
+ * The maximum number of relations that should be fetched in any one
+ * getAttributeStats() call.
+ */
+
+#define MAX_ATTR_STATS_RELS 100
+
 /*
  * The default number of rows per INSERT when
  * --inserts is specified without --rows-per-insert
@@ -222,6 +253,8 @@ static int	nsequences = 0;
  */
 #define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000
 
+
+
 /*
  * Macro for producing quoted, schema-qualified name of a dumpable object.
  */
@@ -399,6 +432,9 @@ static void setupDumpWorker(Archive *AH);
 static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
 static bool forcePartitionRootLoad(const TableInfo *tbinfo);
 static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static void appendNamedArgument(PQExpBuffer out, Archive *fout,
+								const char *argname, const char *argtype,
+								const char *argval);
 
 
 int
@@ -10560,7 +10596,286 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
 }
 
 /*
- * printDumpRelationStats --
+ * Fetch next batch of rows from getAttributeStats()
+ */
+static void
+fetchNextAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData schemas;
+	PQExpBufferData relations;
+	int			numoids = 0;
+
+	Assert(AH != NULL);
+
+	/* free last result set, if any */
+	if (attrstats.state == STATSBUF_ACTIVE)
+		PQclear(attrstats.res);
+
+	/* If we have looped around to the start of the TOC, restart */
+	if (attrstats.te == AH->toc)
+		attrstats.te = AH->toc->next;
+
+	initPQExpBuffer(&schemas);
+	initPQExpBuffer(&relations);
+
+	/*
+	 * Walk ahead looking for relstats entries that are active in this
+	 * section, adding the names to the schemas and relations lists.
+	 */
+	while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS))
+	{
+		if (attrstats.te->reqs != 0 &&
+			strcmp(attrstats.te->desc, "STATISTICS DATA") == 0)
+		{
+			RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg;
+
+			Assert(rsinfo != NULL);
+
+			if (numoids > 0)
+			{
+				appendPQExpBufferStr(&schemas, ",");
+				appendPQExpBufferStr(&relations, ",");
+			}
+			appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name));
+			numoids++;
+		}
+
+		attrstats.te = attrstats.te->next;
+	}
+
+	if (numoids > 0)
+	{
+		PQExpBufferData query;
+
+		initPQExpBuffer(&query);
+		appendPQExpBuffer(&query,
+						  "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])",
+						  schemas.data, relations.data);
+		attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+		attrstats.idx = 0;
+	}
+	else
+	{
+		attrstats.state = STATSBUF_EXHAUSTED;
+		attrstats.res = NULL;
+		attrstats.idx = -1;
+	}
+
+	termPQExpBuffer(&schemas);
+	termPQExpBuffer(&relations);
+}
+
+/*
+ * Prepare the getAttributeStats() statement
+ *
+ * This is done automatically if the user specified dumpStatistics.
+ */
+static void
+initAttributeStats(Archive *fout)
+{
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
+	PQExpBufferData query;
+
+	Assert(AH != NULL);
+	initPQExpBuffer(&query);
+
+	appendPQExpBufferStr(&query,
+						 "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n"
+						 "SELECT s.schemaname, s.tablename, s.attname, s.inherited, "
+						 "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, "
+						 "s.most_common_freqs, s.histogram_bounds, s.correlation, "
+						 "s.most_common_elems, s.most_common_elem_freqs, "
+						 "s.elem_count_histogram, ");
+
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(&query,
+							 "s.range_length_histogram, "
+							 "s.range_empty_frac, "
+							 "s.range_bounds_histogram ");
+	else
+		appendPQExpBufferStr(&query,
+							 "NULL AS range_length_histogram, "
+							 "NULL AS range_empty_frac, "
+							 " NULL AS range_bounds_histogram ");
+
+	/*
+	 * The results must be in the order of relations supplied in the
+	 * parameters to ensure that they are in sync with a walk of the TOC.
+	 *
+	 * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
+	 * is a way to lead the query into using the index
+	 * pg_class_relname_nsp_index which in turn allows the planner to avoid an
+	 * expensive full scan of pg_stats.
+	 *
+	 * We may need to adjust this query for versions that are not so easily
+	 * led.
+	 */
+	appendPQExpBufferStr(&query,
+						 "FROM pg_catalog.pg_stats AS s "
+						 "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) "
+						 "ON s.schemaname = u.schemaname "
+						 "AND s.tablename = u.tablename "
+						 "WHERE s.tablename = ANY($2) "
+						 "ORDER BY u.ord, s.attname, s.inherited");
+
+	ExecuteSqlStatement(fout, query.data);
+
+	termPQExpBuffer(&query);
+
+	attrstats.te = AH->toc->next;
+
+	fetchNextAttributeStats(fout);
+
+	attrstats.state = STATSBUF_ACTIVE;
+}
+
+
+/*
+ * append a single attribute stat to the buffer for this relation.
+ */
+static void
+appendAttributeStats(Archive *fout, PQExpBuffer out,
+					 const RelStatsInfo *rsinfo)
+{
+	PGresult   *res = attrstats.res;
+	int			tup_num = attrstats.idx;
+
+	const char *attname;
+
+	static bool indexes_set = false;
+	static int	i_attname,
+				i_inherited,
+				i_null_frac,
+				i_avg_width,
+				i_n_distinct,
+				i_most_common_vals,
+				i_most_common_freqs,
+				i_histogram_bounds,
+				i_correlation,
+				i_most_common_elems,
+				i_most_common_elem_freqs,
+				i_elem_count_histogram,
+				i_range_length_histogram,
+				i_range_empty_frac,
+				i_range_bounds_histogram;
+
+	if (!indexes_set)
+	{
+		/*
+		 * It's a prepared statement, so the indexes will be the same for all
+		 * result sets, so we only need to set them once.
+		 */
+		i_attname = PQfnumber(res, "attname");
+		i_inherited = PQfnumber(res, "inherited");
+		i_null_frac = PQfnumber(res, "null_frac");
+		i_avg_width = PQfnumber(res, "avg_width");
+		i_n_distinct = PQfnumber(res, "n_distinct");
+		i_most_common_vals = PQfnumber(res, "most_common_vals");
+		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
+		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
+		i_correlation = PQfnumber(res, "correlation");
+		i_most_common_elems = PQfnumber(res, "most_common_elems");
+		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
+		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
+		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
+		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
+		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
+		indexes_set = true;
+	}
+
+	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+					  fout->remoteVersion);
+	appendPQExpBufferStr(out, "\t'schemaname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout);
+	appendPQExpBufferStr(out, ",\n\t'relname', ");
+	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
+
+	if (PQgetisnull(res, tup_num, i_attname))
+		pg_fatal("attname cannot be NULL");
+	attname = PQgetvalue(res, tup_num, i_attname);
+
+	/*
+	 * Indexes look up attname in indAttNames to derive attnum, all others use
+	 * attname directly.  We must specify attnum for indexes, since their
+	 * attnames are not necessarily stable across dump/reload.
+	 */
+	if (rsinfo->nindAttNames == 0)
+	{
+		appendPQExpBuffer(out, ",\n\t'attname', ");
+		appendStringLiteralAH(out, attname, fout);
+	}
+	else
+	{
+		bool		found = false;
+
+		for (int i = 0; i < rsinfo->nindAttNames; i++)
+			if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
+			{
+				appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint",
+								  i + 1);
+				found = true;
+				break;
+			}
+
+		if (!found)
+			pg_fatal("could not find index attname \"%s\"", attname);
+	}
+
+	if (!PQgetisnull(res, tup_num, i_inherited))
+		appendNamedArgument(out, fout, "inherited", "boolean",
+							PQgetvalue(res, tup_num, i_inherited));
+	if (!PQgetisnull(res, tup_num, i_null_frac))
+		appendNamedArgument(out, fout, "null_frac", "real",
+							PQgetvalue(res, tup_num, i_null_frac));
+	if (!PQgetisnull(res, tup_num, i_avg_width))
+		appendNamedArgument(out, fout, "avg_width", "integer",
+							PQgetvalue(res, tup_num, i_avg_width));
+	if (!PQgetisnull(res, tup_num, i_n_distinct))
+		appendNamedArgument(out, fout, "n_distinct", "real",
+							PQgetvalue(res, tup_num, i_n_distinct));
+	if (!PQgetisnull(res, tup_num, i_most_common_vals))
+		appendNamedArgument(out, fout, "most_common_vals", "text",
+							PQgetvalue(res, tup_num, i_most_common_vals));
+	if (!PQgetisnull(res, tup_num, i_most_common_freqs))
+		appendNamedArgument(out, fout, "most_common_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_freqs));
+	if (!PQgetisnull(res, tup_num, i_histogram_bounds))
+		appendNamedArgument(out, fout, "histogram_bounds", "text",
+							PQgetvalue(res, tup_num, i_histogram_bounds));
+	if (!PQgetisnull(res, tup_num, i_correlation))
+		appendNamedArgument(out, fout, "correlation", "real",
+							PQgetvalue(res, tup_num, i_correlation));
+	if (!PQgetisnull(res, tup_num, i_most_common_elems))
+		appendNamedArgument(out, fout, "most_common_elems", "text",
+							PQgetvalue(res, tup_num, i_most_common_elems));
+	if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs))
+		appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]",
+							PQgetvalue(res, tup_num, i_most_common_elem_freqs));
+	if (!PQgetisnull(res, tup_num, i_elem_count_histogram))
+		appendNamedArgument(out, fout, "elem_count_histogram", "real[]",
+							PQgetvalue(res, tup_num, i_elem_count_histogram));
+	if (fout->remoteVersion >= 170000)
+	{
+		if (!PQgetisnull(res, tup_num, i_range_length_histogram))
+			appendNamedArgument(out, fout, "range_length_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_length_histogram));
+		if (!PQgetisnull(res, tup_num, i_range_empty_frac))
+			appendNamedArgument(out, fout, "range_empty_frac", "real",
+								PQgetvalue(res, tup_num, i_range_empty_frac));
+		if (!PQgetisnull(res, tup_num, i_range_bounds_histogram))
+			appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+								PQgetvalue(res, tup_num, i_range_bounds_histogram));
+	}
+	appendPQExpBufferStr(out, "\n);\n");
+}
+
+
+
+/*
+ * printRelationStats --
  *
  * Generate the SQL statements needed to restore a relation's statistics.
  */
@@ -10568,64 +10883,21 @@ static char *
 printRelationStats(Archive *fout, const void *userArg)
 {
 	const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
-	const DumpableObject *dobj = &rsinfo->dobj;
+	const DumpableObject *dobj;
+	const char *relschema;
+	const char *relname;
+
+	ArchiveHandle *AH = (ArchiveHandle *) fout;
 
-	PQExpBufferData query;
 	PQExpBufferData out;
 
-	PGresult   *res;
-
-	static bool first_query = true;
-	static int	i_attname;
-	static int	i_inherited;
-	static int	i_null_frac;
-	static int	i_avg_width;
-	static int	i_n_distinct;
-	static int	i_most_common_vals;
-	static int	i_most_common_freqs;
-	static int	i_histogram_bounds;
-	static int	i_correlation;
-	static int	i_most_common_elems;
-	static int	i_most_common_elem_freqs;
-	static int	i_elem_count_histogram;
-	static int	i_range_length_histogram;
-	static int	i_range_empty_frac;
-	static int	i_range_bounds_histogram;
-
-	initPQExpBuffer(&query);
-
-	if (first_query)
-	{
-		appendPQExpBufferStr(&query,
-							 "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n"
-							 "SELECT s.attname, s.inherited, "
-							 "s.null_frac, s.avg_width, s.n_distinct, "
-							 "s.most_common_vals, s.most_common_freqs, "
-							 "s.histogram_bounds, s.correlation, "
-							 "s.most_common_elems, s.most_common_elem_freqs, "
-							 "s.elem_count_histogram, ");
-
-		if (fout->remoteVersion >= 170000)
-			appendPQExpBufferStr(&query,
-								 "s.range_length_histogram, "
-								 "s.range_empty_frac, "
-								 "s.range_bounds_histogram ");
-		else
-			appendPQExpBufferStr(&query,
-								 "NULL AS range_length_histogram,"
-								 "NULL AS range_empty_frac,"
-								 "NULL AS range_bounds_histogram ");
-
-		appendPQExpBufferStr(&query,
-							 "FROM pg_catalog.pg_stats s "
-							 "WHERE s.schemaname = $1 "
-							 "AND s.tablename = $2 "
-							 "ORDER BY s.attname, s.inherited");
-
-		ExecuteSqlStatement(fout, query.data);
-
-		resetPQExpBuffer(&query);
-	}
+	Assert(rsinfo != NULL);
+	dobj = &rsinfo->dobj;
+	Assert(dobj != NULL);
+	relschema = dobj->namespace->dobj.name;
+	Assert(relschema != NULL);
+	relname = dobj->name;
+	Assert(relname != NULL);
 
 	initPQExpBuffer(&out);
 
@@ -10646,132 +10918,72 @@ printRelationStats(Archive *fout, const void *userArg)
 	appendPQExpBufferStr(&out, "\n);\n");
 
 
-	/* fetch attribute stats */
-	appendPQExpBufferStr(&query, "EXECUTE getAttributeStats(");
-	appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout);
-	appendPQExpBufferStr(&query, ", ");
-	appendStringLiteralAH(&query, dobj->name, fout);
-	appendPQExpBufferStr(&query, ")");
+	AH->txnCount++;
 
-	res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK);
+	if (attrstats.state == STATSBUF_UNINITIALIZED)
+		initAttributeStats(fout);
 
-	if (first_query)
+	/*
+	 * Because the query returns rows in the same order as the relations
+	 * requested, and because every relation gets at least one row in the
+	 * result set, the first row for this relation must correspond either to
+	 * the current row of this result set (if one exists) or the first row of
+	 * the next result set (if this one is already consumed).
+	 */
+	if (attrstats.state != STATSBUF_ACTIVE)
+		pg_fatal("Exhausted getAttributeStats() before processing %s.%s",
+				 rsinfo->dobj.namespace->dobj.name,
+				 rsinfo->dobj.name);
+
+	/*
+	 * If the current result set has been fully consumed, then the row(s) we
+	 * need (if any) would be found in the next one. This will update
+	 * attrstats.res and attrstats.idx.
+	 */
+	if (PQntuples(attrstats.res) <= attrstats.idx)
+		fetchNextAttributeStats(fout);
+
+	while (true)
 	{
-		i_attname = PQfnumber(res, "attname");
-		i_inherited = PQfnumber(res, "inherited");
-		i_null_frac = PQfnumber(res, "null_frac");
-		i_avg_width = PQfnumber(res, "avg_width");
-		i_n_distinct = PQfnumber(res, "n_distinct");
-		i_most_common_vals = PQfnumber(res, "most_common_vals");
-		i_most_common_freqs = PQfnumber(res, "most_common_freqs");
-		i_histogram_bounds = PQfnumber(res, "histogram_bounds");
-		i_correlation = PQfnumber(res, "correlation");
-		i_most_common_elems = PQfnumber(res, "most_common_elems");
-		i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs");
-		i_elem_count_histogram = PQfnumber(res, "elem_count_histogram");
-		i_range_length_histogram = PQfnumber(res, "range_length_histogram");
-		i_range_empty_frac = PQfnumber(res, "range_empty_frac");
-		i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
-		first_query = false;
-	}
-
-	/* restore attribute stats */
-	for (int rownum = 0; rownum < PQntuples(res); rownum++)
-	{
-		const char *attname;
-
-		appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n",
-						  fout->remoteVersion);
-		appendPQExpBufferStr(&out, "\t'schemaname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout);
-		appendPQExpBufferStr(&out, ",\n\t'relname', ");
-		appendStringLiteralAH(&out, rsinfo->dobj.name, fout);
-
-		if (PQgetisnull(res, rownum, i_attname))
-			pg_fatal("attname cannot be NULL");
-		attname = PQgetvalue(res, rownum, i_attname);
+		int			i_schemaname;
+		int			i_tablename;
+		char	   *schemaname;
+		char	   *tablename;	/* misnomer, following pg_stats naming */
 
 		/*
-		 * Indexes look up attname in indAttNames to derive attnum, all others
-		 * use attname directly.  We must specify attnum for indexes, since
-		 * their attnames are not necessarily stable across dump/reload.
+		 * If we hit the end of the result set, then there are no more records
+		 * for this relation, so we should stop, but first get the next result
+		 * set for the next batch of relations.
 		 */
-		if (rsinfo->nindAttNames == 0)
+		if (PQntuples(attrstats.res) <= attrstats.idx)
 		{
-			appendPQExpBuffer(&out, ",\n\t'attname', ");
-			appendStringLiteralAH(&out, attname, fout);
-		}
-		else
-		{
-			bool		found = false;
-
-			for (int i = 0; i < rsinfo->nindAttNames; i++)
-			{
-				if (strcmp(attname, rsinfo->indAttNames[i]) == 0)
-				{
-					appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint",
-									  i + 1);
-					found = true;
-					break;
-				}
-			}
-
-			if (!found)
-				pg_fatal("could not find index attname \"%s\"", attname);
+			fetchNextAttributeStats(fout);
+			break;
 		}
 
-		if (!PQgetisnull(res, rownum, i_inherited))
-			appendNamedArgument(&out, fout, "inherited", "boolean",
-								PQgetvalue(res, rownum, i_inherited));
-		if (!PQgetisnull(res, rownum, i_null_frac))
-			appendNamedArgument(&out, fout, "null_frac", "real",
-								PQgetvalue(res, rownum, i_null_frac));
-		if (!PQgetisnull(res, rownum, i_avg_width))
-			appendNamedArgument(&out, fout, "avg_width", "integer",
-								PQgetvalue(res, rownum, i_avg_width));
-		if (!PQgetisnull(res, rownum, i_n_distinct))
-			appendNamedArgument(&out, fout, "n_distinct", "real",
-								PQgetvalue(res, rownum, i_n_distinct));
-		if (!PQgetisnull(res, rownum, i_most_common_vals))
-			appendNamedArgument(&out, fout, "most_common_vals", "text",
-								PQgetvalue(res, rownum, i_most_common_vals));
-		if (!PQgetisnull(res, rownum, i_most_common_freqs))
-			appendNamedArgument(&out, fout, "most_common_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_freqs));
-		if (!PQgetisnull(res, rownum, i_histogram_bounds))
-			appendNamedArgument(&out, fout, "histogram_bounds", "text",
-								PQgetvalue(res, rownum, i_histogram_bounds));
-		if (!PQgetisnull(res, rownum, i_correlation))
-			appendNamedArgument(&out, fout, "correlation", "real",
-								PQgetvalue(res, rownum, i_correlation));
-		if (!PQgetisnull(res, rownum, i_most_common_elems))
-			appendNamedArgument(&out, fout, "most_common_elems", "text",
-								PQgetvalue(res, rownum, i_most_common_elems));
-		if (!PQgetisnull(res, rownum, i_most_common_elem_freqs))
-			appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]",
-								PQgetvalue(res, rownum, i_most_common_elem_freqs));
-		if (!PQgetisnull(res, rownum, i_elem_count_histogram))
-			appendNamedArgument(&out, fout, "elem_count_histogram", "real[]",
-								PQgetvalue(res, rownum, i_elem_count_histogram));
-		if (fout->remoteVersion >= 170000)
-		{
-			if (!PQgetisnull(res, rownum, i_range_length_histogram))
-				appendNamedArgument(&out, fout, "range_length_histogram", "text",
-									PQgetvalue(res, rownum, i_range_length_histogram));
-			if (!PQgetisnull(res, rownum, i_range_empty_frac))
-				appendNamedArgument(&out, fout, "range_empty_frac", "real",
-									PQgetvalue(res, rownum, i_range_empty_frac));
-			if (!PQgetisnull(res, rownum, i_range_bounds_histogram))
-				appendNamedArgument(&out, fout, "range_bounds_histogram", "text",
-									PQgetvalue(res, rownum, i_range_bounds_histogram));
-		}
-		appendPQExpBufferStr(&out, "\n);\n");
+		i_schemaname = PQfnumber(attrstats.res, "schemaname");
+		Assert(i_schemaname >= 0);
+		i_tablename = PQfnumber(attrstats.res, "tablename");
+		Assert(i_tablename >= 0);
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname))
+			pg_fatal("getAttributeStats() schemaname cannot be NULL");
+
+		if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename))
+			pg_fatal("getAttributeStats() tablename cannot be NULL");
+
+		schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname);
+		tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename);
+
+		/* stop if current stat row isn't for this relation */
+		if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0)
+			break;
+
+		appendAttributeStats(fout, &out, rsinfo);
+		AH->txnCount++;
+		attrstats.idx++;
 	}
 
-	PQclear(res);
-
-	termPQExpBuffer(&query);
 	return out.data;
 }
 
-- 
2.49.0



  [text/x-patch] v12-0003-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch (30.4K, ../../CADkLM=domd5+CvjKMHGbOfvSuY6J8G-x+9M2D6Ss2HamYefE9w@mail.gmail.com/5-v12-0003-Downgrade-many-pg_restore_-_stats-errors-to-warn.patch)
  download | inline diff:
From 16794820dedd79ec58f8692da5b50a4d8976620a Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 8 Mar 2025 00:52:41 -0500
Subject: [PATCH v12 3/3] Downgrade many pg_restore_*_stats errors to warnings.

We want to avoid errors that can potentially stop an otherwise
successful pg_upgrade or pg_restore operation. With that in mind, change
as many ERROR reports to WARNING + early termination with no data
updated.
---
 src/include/statistics/stat_utils.h        |   4 +-
 src/backend/statistics/attribute_stats.c   | 120 ++++++++++----
 src/backend/statistics/relation_stats.c    |  12 +-
 src/backend/statistics/stat_utils.c        |  65 ++++++--
 src/test/regress/expected/stats_import.out | 184 ++++++++++++++++-----
 src/test/regress/sql/stats_import.sql      |  36 ++--
 6 files changed, 309 insertions(+), 112 deletions(-)

diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 512eb776e0e..809c8263a41 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -21,7 +21,7 @@ struct StatsArgInfo
 	Oid			argtype;
 };
 
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern bool stats_check_required_arg(FunctionCallInfo fcinfo,
 									 struct StatsArgInfo *arginfo,
 									 int argnum);
 extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
@@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
 								 struct StatsArgInfo *arginfo,
 								 int argnum1, int argnum2);
 
-extern void stats_lock_check_privileges(Oid reloid);
+extern bool stats_lock_check_privileges(Oid reloid);
 
 extern Oid	stats_lookup_relid(const char *nspname, const char *relname);
 
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f5eb17ba42d..b7ba1622391 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] =
 
 static bool attribute_statistics_update(FunctionCallInfo fcinfo);
 static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
+static bool get_attr_stat_type(Oid reloid, AttrNumber attnum,
 							   Oid *atttypid, int32 *atttypmod,
 							   char *atttyptype, Oid *atttypcoll,
 							   Oid *eq_opr, Oid *lt_opr);
@@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
  * stored as an anyarray, and the representation of the array needs to store
  * the correct element type, which must be derived from the attribute.
  *
- * Major errors, such as the table not existing, the attribute not existing,
- * or a permissions failure are always reported at ERROR. Other errors, such
- * as a conversion failure on one statistic kind, are reported as a WARNING
- * and other statistic kinds may still be updated.
+ * This function is called during database upgrades and restorations, therefore
+ * it is imperative to avoid ERRORs that could potentially end the upgrade or
+ * restore unless. Major errors, such as the table not existing, the attribute
+ * not existing, or permissions failure are reported as WARNINGs with an end to
+ * the function, thus allowing the upgrade/restore to continue, but without the
+ * stats that can be regenereated once the database is online again.
  */
 static bool
 attribute_statistics_update(FunctionCallInfo fcinfo)
@@ -148,8 +150,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	HeapTuple	statup;
 
 	Oid			atttypid = InvalidOid;
-	int32		atttypmod;
-	char		atttyptype;
+	int32		atttypmod = -1;
+	char		atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */
 	Oid			atttypcoll = InvalidOid;
 	Oid			eq_opr = InvalidOid;
 	Oid			lt_opr = InvalidOid;
@@ -176,38 +178,52 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG))
+		return false;
+	if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		return false;
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		return false;
+	}
 
 	/* lock before looking up attribute */
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	/* user can specify either attname or attnum, but not both */
 	if (!PG_ARGISNULL(ATTNAME_ARG))
 	{
 		if (!PG_ARGISNULL(ATTNUM_ARG))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both attname and attnum")));
+			return false;
+		}
 		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" of relation \"%s\" does not exist",
 							attname, relname)));
+			return false;
+		}
 	}
 	else if (!PG_ARGISNULL(ATTNUM_ARG))
 	{
@@ -216,27 +232,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		/* annoyingly, get_attname doesn't check attisdropped */
 		if (attname == NULL ||
 			!SearchSysCacheExistsAttName(reloid, attname))
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column %d of relation \"%s\" does not exist",
 							attnum, relname)));
+			return false;
+		}
 	}
 	else
 	{
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("must specify either attname or attnum")));
-		attname = NULL;			/* keep compiler quiet */
-		attnum = 0;
+		return false;
 	}
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
+		return false;
+	}
 
-	stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG))
+		return false;
 	inherited = PG_GETARG_BOOL(INHERITED_ARG);
 
 	/*
@@ -285,10 +307,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* derive information from attribute */
-	get_attr_stat_type(reloid, attnum,
-					   &atttypid, &atttypmod,
-					   &atttyptype, &atttypcoll,
-					   &eq_opr, &lt_opr);
+	if (!get_attr_stat_type(reloid, attnum,
+							&atttypid, &atttypmod,
+							&atttyptype, &atttypcoll,
+							&eq_opr, &lt_opr))
+		result = false;
 
 	/* if needed, derive element type */
 	if (do_mcelem || do_dechist)
@@ -568,7 +591,7 @@ get_attr_expr(Relation rel, int attnum)
 /*
  * Derive type information from the attribute.
  */
-static void
+static bool
 get_attr_stat_type(Oid reloid, AttrNumber attnum,
 				   Oid *atttypid, int32 *atttypmod,
 				   char *atttyptype, Oid *atttypcoll,
@@ -585,18 +608,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 
 	/* Attribute not found */
 	if (!HeapTupleIsValid(atup))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	attr = (Form_pg_attribute) GETSTRUCT(atup);
 
 	if (attr->attisdropped)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("attribute %d of relation \"%s\" does not exist",
 						attnum, RelationGetRelationName(rel))));
+		relation_close(rel, NoLock);
+		return false;
+	}
 
 	expr = get_attr_expr(rel, attr->attnum);
 
@@ -645,6 +676,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum,
 		*atttypcoll = DEFAULT_COLLATION_OID;
 
 	relation_close(rel, NoLock);
+	return true;
 }
 
 /*
@@ -770,6 +802,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces,
 	if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
 		slotidx = first_empty;
 
+	/*
+	 * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS
+	 * statistic kinds, so this can safely remain an ERROR for now.
+	 */
 	if (slotidx >= STATISTIC_NUM_SLOTS)
 		ereport(ERROR,
 				(errmsg("maximum number of statistics slots exceeded: %d",
@@ -915,38 +951,54 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	AttrNumber	attnum;
 	bool		inherited;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG))
+		PG_RETURN_VOID();
+	if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG))
+		PG_RETURN_VOID();
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		PG_RETURN_VOID();
 
 	if (RecoveryInProgress())
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
+		PG_RETURN_VOID();
+	}
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		PG_RETURN_VOID();
 
 	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
 	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot clear statistics on system column \"%s\"",
 						attname)));
+		PG_RETURN_VOID();
+	}
 
 	if (attnum == InvalidAttrNumber)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
 						attname, get_rel_name(reloid))));
+		PG_RETURN_VOID();
+	}
 
 	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
 
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index cd3a75b621a..7c47af15c9f 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -83,13 +83,18 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
 
-	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG))
+		return false;
+
+	if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG))
+		return false;
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
 
 	reloid = stats_lookup_relid(nspname, relname);
+	if (!OidIsValid(reloid))
+		return false;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -97,7 +102,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("recovery is in progress"),
 				 errhint("Statistics cannot be modified during recovery.")));
 
-	stats_lock_check_privileges(reloid);
+	if (!stats_lock_check_privileges(reloid))
+		return false;
 
 	if (!PG_ARGISNULL(RELPAGES_ARG))
 	{
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index a9a3224efe6..d587e875457 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -33,16 +33,20 @@
 /*
  * Ensure that a given argument is not null.
  */
-void
+bool
 stats_check_required_arg(FunctionCallInfo fcinfo,
 						 struct StatsArgInfo *arginfo,
 						 int argnum)
 {
 	if (PG_ARGISNULL(argnum))
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("\"%s\" cannot be NULL",
 						arginfo[argnum].argname)));
+		return false;
+	}
+	return true;
 }
 
 /*
@@ -127,13 +131,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo,
  *   - the role owns the current database and the relation is not shared
  *   - the role has the MAINTAIN privilege on the relation
  */
-void
+bool
 stats_lock_check_privileges(Oid reloid)
 {
 	Relation	table;
 	Oid			table_oid = reloid;
 	Oid			index_oid = InvalidOid;
 	LOCKMODE	index_lockmode = NoLock;
+	bool		ok = true;
 
 	/*
 	 * For indexes, we follow the locking behavior in do_analyze_rel() and
@@ -173,14 +178,15 @@ stats_lock_check_privileges(Oid reloid)
 		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
-			ereport(ERROR,
+			ereport(WARNING,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot modify statistics for relation \"%s\"",
 							RelationGetRelationName(table)),
 					 errdetail_relkind_not_supported(table->rd_rel->relkind)));
+		ok = false;
 	}
 
-	if (OidIsValid(index_oid))
+	if (ok && (OidIsValid(index_oid)))
 	{
 		Relation	index;
 
@@ -193,25 +199,33 @@ stats_lock_check_privileges(Oid reloid)
 		relation_close(index, NoLock);
 	}
 
-	if (table->rd_rel->relisshared)
-		ereport(ERROR,
+	if (ok && (table->rd_rel->relisshared))
+	{
+		ereport(WARNING,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics for shared relation")));
+		ok = false;
+	}
 
-	if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+	if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())))
 	{
 		AclResult	aclresult = pg_class_aclcheck(RelationGetRelid(table),
 												  GetUserId(),
 												  ACL_MAINTAIN);
 
 		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult,
-						   get_relkind_objtype(table->rd_rel->relkind),
-						   NameStr(table->rd_rel->relname));
+		{
+			ereport(WARNING,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						errmsg("permission denied for relation %s",
+							   NameStr(table->rd_rel->relname))));
+			ok = false;
+		}
 	}
 
 	/* retain lock on table */
 	relation_close(table, NoLock);
+	return ok;
 }
 
 /*
@@ -223,10 +237,20 @@ stats_lookup_relid(const char *nspname, const char *relname)
 	Oid			nspoid;
 	Oid			reloid;
 
-	nspoid = LookupExplicitNamespace(nspname, false);
+	nspoid = LookupExplicitNamespace(nspname, true);
+	if (!OidIsValid(nspoid))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+				 errmsg("relation \"%s.%s\" does not exist",
+						nspname, relname)));
+
+		return InvalidOid;
+	}
+
 	reloid = get_relname_relid(relname, nspoid);
 	if (!OidIsValid(reloid))
-		ereport(ERROR,
+		ereport(WARNING,
 				(errcode(ERRCODE_UNDEFINED_TABLE),
 				 errmsg("relation \"%s.%s\" does not exist",
 						nspname, relname)));
@@ -303,9 +327,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 								  &args, &types, &argnulls);
 
 	if (nargs % 2 != 0)
-		ereport(ERROR,
+	{
+		ereport(WARNING,
 				errmsg("variadic arguments must be name/value pairs"),
 				errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+		return false;
+	}
 
 	/*
 	 * For each argument name/value pair, find corresponding positional
@@ -318,14 +345,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 		char	   *argname;
 
 		if (argnulls[i])
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d is NULL", i + 1)));
+			return false;
+		}
 
 		if (types[i] != TEXTOID)
-			ereport(ERROR,
+		{
+			ereport(WARNING,
 					(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
 							i + 1, format_type_be(types[i]),
 							format_type_be(TEXTOID))));
+			return false;
+		}
 
 		if (argnulls[i + 1])
 			continue;
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 48d6392b4ad..161cf67b711 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -46,49 +46,85 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 --
 -- relstats tests
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
-ERROR:  "schemaname" cannot be NULL
--- error: relname missing
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
-ERROR:  "relname" cannot be NULL
---- error: schemaname is wrong type
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 WARNING:  argument "schemaname" has type "double precision", expected type "text"
-ERROR:  "schemaname" cannot be NULL
---- error: relname is wrong type
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 WARNING:  argument "relname" has type "oid", expected type "text"
-ERROR:  "relname" cannot be NULL
--- error: relation not found
+WARNING:  "relname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
-ERROR:  relation "stats_import.nope" does not exist
--- error: odd number of variadic arguments cannot be pairs
+WARNING:  relation "stats_import.nope" does not exist
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
-ERROR:  variadic arguments must be name/value pairs
+WARNING:  variadic arguments must be name/value pairs
 HINT:  Provide an even number of variadic arguments that can be divided into pairs.
--- error: argument name is NULL
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         NULL, '17'::integer);
-ERROR:  name at variadic position 5 is NULL
+WARNING:  name at variadic position 5 is NULL
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 -- starting stats
 SELECT relpages, reltuples, relallvisible, relallfrozen
 FROM pg_class
@@ -340,65 +376,110 @@ CREATE SEQUENCE stats_import.testseq;
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_restore_relation_stats 
+---------------------------
+ f
+(1 row)
+
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
-ERROR:  cannot modify statistics for relation "testseq"
+WARNING:  cannot modify statistics for relation "testseq"
 DETAIL:  This operation is not supported for sequences.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
 SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
-ERROR:  cannot modify statistics for relation "testview"
+WARNING:  cannot modify statistics for relation "testview"
 DETAIL:  This operation is not supported for views.
+ pg_clear_relation_stats 
+-------------------------
+ 
+(1 row)
+
 --
 -- attribute stats
 --
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "schemaname" cannot be NULL
--- error: schema does not exist
+WARNING:  "schemaname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  schema "nope" does not exist
--- error: relname missing
+WARNING:  relation "nope.test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: relname does not exist
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  relation "stats_import.nope" does not exist
--- error: relname null
+WARNING:  relation "stats_import.nope" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  "relname" cannot be NULL
--- error: NULL attname
+WARNING:  "relname" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', NULL,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attname doesn't exist
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -407,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'null_frac', 0.1::real,
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
-ERROR:  column "nope" of relation "test" does not exist
--- error: both attname and attnum
+WARNING:  column "nope" of relation "test" does not exist
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -416,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'attnum', 1::smallint,
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot specify both attname and attnum
--- error: neither attname nor attnum
+WARNING:  cannot specify both attname and attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  must specify either attname or attnum
--- error: attribute is system column
+WARNING:  must specify either attname or attnum
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'xmin',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
-ERROR:  cannot modify statistics on system column "xmin"
--- error: inherited null
+WARNING:  cannot modify statistics on system column "xmin"
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'attname', 'id',
     'inherited', NULL::boolean,
     'null_frac', 0.1::real);
-ERROR:  "inherited" cannot be NULL
+WARNING:  "inherited" cannot be NULL
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
 -- ok: just the fixed values, with version, no stakinds
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index d140733a750..be8045ceea5 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test');
 -- relstats tests
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'relname', 'test',
         'relpages', 17::integer);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relpages', 17::integer);
 
---- error: schemaname is wrong type
+--- warning: schemaname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 3.6::float,
         'relname', 'test',
         'relpages', 17::integer);
 
---- error: relname is wrong type
+--- warning: relname is wrong type, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 0::oid,
         'relpages', 17::integer);
 
--- error: relation not found
+-- warning: relation not found, nothing updated
 SELECT pg_catalog.pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'nope',
         'relpages', 17::integer);
 
--- error: odd number of variadic arguments cannot be pairs
+-- warning: odd number of variadic arguments cannot be pairs, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
         'relallvisible');
 
--- error: argument name is NULL
+-- warning: argument name is NULL, nothing updated
 SELECT pg_restore_relation_stats(
         'schemaname', 'stats_import',
         'relname', 'test',
@@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname
 -- attribute stats
 --
 
--- error: schemaname missing
+-- warning: schemaname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'relname', 'test',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: schema does not exist
+-- warning: schema does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'nope',
     'relname', 'test',
@@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname missing
+-- warning: relname missing, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'attname', 'id',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname does not exist
+-- warning: relname does not exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'nope',
@@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: relname null
+-- warning: relname null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', NULL,
@@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: NULL attname
+-- warning: NULL attname, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attname doesn't exist
+-- warning: attname doesn't exist, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'avg_width', 2::integer,
     'n_distinct', 0.3::real);
 
--- error: both attname and attnum
+-- warning: both attname and attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: neither attname nor attnum
+-- warning: neither attname nor attnum, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: attribute is system column
+-- warning: attribute is system column, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
@@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'inherited', false::boolean,
     'null_frac', 0.1::real);
 
--- error: inherited null
+-- warning: inherited null, nothing updated
 SELECT pg_catalog.pg_restore_attribute_stats(
     'schemaname', 'stats_import',
     'relname', 'test',
-- 
2.49.0



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-04-01 02:33                                       ` Nathan Bossart <[email protected]>
  2025-04-01 03:02                                         ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Nathan Bossart @ 2025-04-01 02:33 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Mon, Mar 31, 2025 at 11:11:47AM -0400, Corey Huinker wrote:
> In light of v11-0001 being committed as 4694aedf63bf, I've rebased the
> remaining patches.

I spent the day preparing these for commit.  A few notes:

* I've added a new prerequisite patch that skips the second WriteToc() call
  for custom-format dumps that do not include data.  After some testing and
  code analysis, I haven't identified any examples where this produces
  different output.  This doesn't help much on its own, but it will become
  rather important when we move the attribute statistics queries to happen
  within WriteToc() in 0002.

* I was a little worried about the correctness of 0002 for dumps that run
  the attribute statistics queries twice, but I couldn't identify any
  problems here either.

* I removed a lot of miscellaneous refactoring that seemed unnecessary for
  these patches.  Let's move that to another patch set and keep these as
  simple as possible.

* I made a small adjustment to the TOC scan restarting logic in
  fetchAttributeStats().  Specifically, we now only allow the scan to
  restart once for custom-format dumps that include data.

* While these patches help decrease pg_dump's memory footprint, I believe
  pg_restore still reads the entire TOC into memory.  That's not this patch
  set's problem, but I think it's still an important consideration for the
  bigger picture.

Regarding whether pg_dump should dump statistics by default, my current
thinking is that it shouldn't, but I think we _should_ have pg_upgrade
dump/restore statistics by default because that is arguably the most
important use-case.  This is more a gut feeling than anything, so I reserve
the right to change my opinion.

My goal is to commit the attached patches on Friday morning, but of course
that is subject to change based on any feedback or objections that emerge
in the meantime.

-- 
nathan


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-01 03:02                                         ` Robert Treat <[email protected]>
  1 sibling, 0 replies; 87+ messages in thread

From: Robert Treat @ 2025-04-01 03:02 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Mon, Mar 31, 2025 at 10:33 PM Nathan Bossart
<[email protected]> wrote:
> On Mon, Mar 31, 2025 at 11:11:47AM -0400, Corey Huinker wrote:
> Regarding whether pg_dump should dump statistics by default, my current
> thinking is that it shouldn't, but I think we _should_ have pg_upgrade
> dump/restore statistics by default because that is arguably the most
> important use-case.  This is more a gut feeling than anything, so I reserve
> the right to change my opinion.
>

I did some mental exercises on a number of different use cases and
scenarios (pagila work, pgextractor type stuff, backups, etc...) and I
couldn't come up with any strong arguments against including the stats
by default, generally because I think when your process needs to care
about the output of pg_dump, it seems like most cases require enough
specificity that this wouldn't actually break that.

Still, I am sympathetic to Greg's earlier concerns on the topic, but
would also agree it seems like a clear win for pg_upgrade, so I think
our gut feelings might actually be aligned on this one ;-)


Robert Treat
https://xzilla.net





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-01 18:20                                         ` Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-01 18:20 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Mon, Mar 31, 2025 at 09:33:15PM -0500, Nathan Bossart wrote:
> My goal is to commit the attached patches on Friday morning, but of course
> that is subject to change based on any feedback or objections that emerge
> in the meantime.

I spent some more time polishing these patches this morning.  There should
be no functional differences, but I did restructure 0003 to make it even
simpler.

-- 
nathan


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-01 18:44                                           ` Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-01 18:44 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Tue, Apr 01, 2025 at 01:20:30PM -0500, Nathan Bossart wrote:
> On Mon, Mar 31, 2025 at 09:33:15PM -0500, Nathan Bossart wrote:
>> My goal is to commit the attached patches on Friday morning, but of course
>> that is subject to change based on any feedback or objections that emerge
>> in the meantime.
> 
> I spent some more time polishing these patches this morning.  There should
> be no functional differences, but I did restructure 0003 to make it even
> simpler.

Apologies for the noise.  I noticed one more way to simplify 0002.  As
before, there should be no functional differences.

-- 
nathan


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-02 03:21                                             ` Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:38                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Nathan Bossart @ 2025-04-02 03:21 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Tue, Apr 01, 2025 at 03:05:59PM -0700, Jeff Davis wrote:
> To restate the problem: one of the problems being solved here is that
> the existing code for custom-format dumps calls WriteToc twice. That
> was not a big problem before this patch, when the contents of the
> entries was easily accessible in memory. But the point of 0002 is to
> avoid keeping all of the stats in memory at once, because that causes
> bloat; and instead to query it on demand.
> 
> In theory, we could fix the pre-existing code by making the second pass
> able to jump over the other contents of the entry and just update the
> data offsets. But that seems invasive, at least to do it properly.
> 
> 0001 sidesteps the problem by skipping the second pass if data's not
> being dumped (because there are no offsets that need updating). The
> worst case is when there are a lot of objects with a small amount of
> data. But that's a worst case for stats in general, so I don't think
> that needs to be solved here.
> 
> Issuing the stats queries twice is not great, though. If there's any
> non-deterministic output in the query, that could lead to strangeness.
> How bad can that be? If the results change in some way that looks
> benign, but changes the length of the definition string, can it lead to
> corruption of a ToC entry? I'm not saying there's a problem, but trying
> to understand the risk of future problems.

It certainly feels risky.  I was able to avoid executing the queries twice
in all cases by saving the definition length in the TOC entry and skipping
that many bytes the second time round.  That's simple enough, but it relies
on various assumptions such as fseeko() being available (IIUC the file will
only be open for writing so we cannot fall back on fread()) and WriteStr()
returning an accurate value (which I'm skeptical of because some formats
compress this data).  But AFAICT custom format is the only format that does
a second WriteToc() pass at the moment, and it only does so when fseeko()
is usable.  Plus, custom format doesn't appear to compress anything written
via WriteStr().

We might be able to improve this by inventing a new callback that fails for
all formats except for custom with feesko() available.  That would at least
ensure hard failures if these assumptions change.  That problably wouldn't
be terribly invasive.  I'm curious what you think.

> For 0003, it makes an assumption about the way the scan happens in
> WriteToc(). Can you add some additional sanity checks to verify that
> something doesn't happen in a different order than we expect?

Hm.  One thing we could do is to send the TocEntry to the callback and
verify that matches the one we were expecting to see next (as set by a
previous call).  Does that sound like a strong enough check?  FWIW the
pg_dump tests failed miserably until Corey and I got this part right, so
our usual tests should also offer some assurance.

> Also, why do we need the clause "WHERE s.tablename = ANY($2)"? Isn't
> that already implied by "JOIN unnest($1, $2) ... s.tablename =
> u.tablename"?

Good question.  Corey, do you recall why this was needed?

-- 
nathan


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-02 05:44                                               ` Jeff Davis <[email protected]>
  2025-04-02 16:42                                                 ` Re: Statistics Import and Export Andres Freund <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  1 sibling, 2 replies; 87+ messages in thread

From: Jeff Davis @ 2025-04-02 05:44 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Tue, 2025-04-01 at 22:21 -0500, Nathan Bossart wrote:
> It certainly feels risky.  I was able to avoid executing the queries
> twice
> in all cases by saving the definition length in the TOC entry and
> skipping
> that many bytes the second time round.

That feels like a better approach.

>   That's simple enough, but it relies
> on various assumptions such as fseeko() being available (IIUC the
> file will
> only be open for writing so we cannot fall back on fread()) and
> WriteStr()
> returning an accurate value (which I'm skeptical of because some
> formats
> compress this data).  But AFAICT custom format is the only format
> that does
> a second WriteToc() pass at the moment, and it only does so when
> fseeko()
> is usable.

Even with those assumptions, I think it's much better than querying
twice and assuming that the results are the same.

>   Plus, custom format doesn't appear to compress anything written
> via WriteStr().

If WriteStr() was doing compression, that would make the second
WriteToc() pass to update the data offsets scary even in the existing
code.

> We might be able to improve this by inventing a new callback that
> fails for
> all formats except for custom with feesko() available.  That would at
> least
> ensure hard failures if these assumptions change.  That problably
> wouldn't
> be terribly invasive.  I'm curious what you think.

That sounds fine, I'd say do that if it feels reasonable, and if the
extra callbacks get too messy, we can just document the assumptions
instead.

> 
> Hm.  One thing we could do is to send the TocEntry to the callback
> and
> verify that matches the one we were expecting to see next (as set by
> a
> previous call).  Does that sound like a strong enough check?

Again, I'd just be practical here and do the check if it feels natural,
and if not, improve the comments so that someone modifying the code
would know where to look.


Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-04-02 16:42                                                 ` Andres Freund <[email protected]>
  1 sibling, 0 replies; 87+ messages in thread

From: Andres Freund @ 2025-04-02 16:42 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Hi,

https://commitfest.postgresql.org/patch/4538/ is still in "needs review", even
though the feature really has been committed.  Is that intention, e.g. to
track pending changes that we're planning to make?

Greetings,

Andres





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-04-03 02:26                                                 ` Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-03 02:26 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Tue, Apr 01, 2025 at 10:44:19PM -0700, Jeff Davis wrote:
> On Tue, 2025-04-01 at 22:21 -0500, Nathan Bossart wrote:
>> We might be able to improve this by inventing a new callback that fails for
>> all formats except for custom with feesko() available.  That would at least
>> ensure hard failures if these assumptions change.  That problably wouldn't
>> be terribly invasive.  I'm curious what you think.
> 
> That sounds fine, I'd say do that if it feels reasonable, and if the
> extra callbacks get too messy, we can just document the assumptions
> instead.

I did write a version with callbacks, but it felt a bit silly because it is
very obviously intended for this one case.  So, I removed them in the
attached patch set.

>> Hm.  One thing we could do is to send the TocEntry to the callback and
>> verify that matches the one we were expecting to see next (as set by a
>> previous call).  Does that sound like a strong enough check?
> 
> Again, I'd just be practical here and do the check if it feels natural,
> and if not, improve the comments so that someone modifying the code
> would know where to look.

Okay, here is an updated patch set.  I did add some verification code,
which ended up being a really good idea because it revealed a couple of
cases we weren't handling:

* Besides custom format calling WriteToc() twice to update the data
  offsets, tar format calls WriteToc() followed by RestoreArchive() to
  write restore.sql.  I couldn't think of a great way to avoid executing
  the queries twice in this case, so I settled on allowing it for only that
  mode.  While we don't expect the second set of queries to result in
  different stats definitions, even if it did, the worst case is that the
  content of restore.sql (which isn't used by pg_restore) would be
  different.  I noticed some past discussion that seems to suggest that
  this format might be a candidate for deprecation [0], so I'm not sure
  it's worth doing anything fancier.

* Our batching code assumes that stats entries are dumped in TOC order,
  which unfortunately wasn't true for formats that use RestoreArchive() for
  dumping.  This is because RestoreArchive() does multiple passes through
  the TOC and selectively dumps certain entries each time.  This is
  particularly troublesome for index stats and a subset of matview stats;
  both are in SECTION_POST_DATA, but matview stats that depend on matview
  data are dumped in RESTORE_PASS_POST_ACL, while all other stats data is
  dumped in RESTORE_PASS_MAIN.  To deal with this, I propose moving all
  stats entries in SECTION_POST_DATA to RESTORE_PASS_POST_ACL, which
  ensures that we always dump stats in TOC order.  One convenient side
  effect of this change is that we can revert a decent chunk of commit
  a0a4601765.  It might be possible to do better via smarter lookahead code
  or a more sophisticated cache, but it's a bit late in the game for that.

[0] https://postgr.es/m/20180727015306.fzlo4inv5i3zqr2c%40alap3.anarazel.de

-- 
nathan


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-03 22:23                                                   ` Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-04-03 22:23 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Wed, 2025-04-02 at 21:26 -0500, Nathan Bossart wrote:

> Okay, here is an updated patch set.


> * Besides custom format calling WriteToc() twice to update the data
>   offsets, tar format ... even if it did, the worst case is that
> the
>   content of restore.sql (which isn't used by pg_restore) would be
>   different.  I noticed some past discussion that seems to suggest
> that
>   this format might be a candidate for deprecation [0], so I'm not
> sure
>   it's worth doing anything fancier.

I agree that the risk for tar format seems much lower.

> * Our batching code assumes that stats entries are dumped in TOC
> order,
> 

...

>  I propose moving all
>   stats entries in SECTION_POST_DATA to RESTORE_PASS_POST_ACL, which
>   ensures that we always dump stats in TOC order.  One convenient
> side
>   effect of this change is that we can revert a decent chunk of
> commit
>   a0a4601765.  It might be possible to do better via smarter
> lookahead code
>   or a more sophisticated cache, but it's a bit late in the game for
> that.

This simplifies commit a0a4601765. I'd break out that simplification as
a separate commit to make it easier to understand what happened. 


In patch 0003, there are quite a few static function-scoped variables,
which is not a style that I'm used to. One idea is to bundle them into
a struct representing the cache state (including enough information to
fetch the next batch), and have a single static variable that points to
that.

Also in 0003, the "next_te" variable is a bit confusing, because it's
actually the last TocEntry, until it's advanced to point to the current
one.

Other than that, looks good to me.

Regards,
	Jeff Davis







^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-04-04 02:19                                                     ` Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-04 02:19 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Thanks for reviewing.

On Thu, Apr 03, 2025 at 03:23:40PM -0700, Jeff Davis wrote:
> This simplifies commit a0a4601765. I'd break out that simplification as
> a separate commit to make it easier to understand what happened. 

Done.

> In patch 0003, there are quite a few static function-scoped variables,
> which is not a style that I'm used to. One idea is to bundle them into
> a struct representing the cache state (including enough information to
> fetch the next batch), and have a single static variable that points to
> that.

As discussed off-list, I didn't take this suggestion for now.  Corey did
this originally, and I converted it to static function-scoped variables 1)
to reduce patch size and 2) because I noticed that each of the state
variables were only needed in one function.  I agree that a struct might be
slightly more readable, but we can always change this in the future if
desired.

> Also in 0003, the "next_te" variable is a bit confusing, because it's
> actually the last TocEntry, until it's advanced to point to the current
> one.

I've renamed it to expected_te.

> Other than that, looks good to me.

Great.  I'm planning to commit the attached patch set tomorrow morning.

For the record, I spent most of today trying very hard to fix the layering
violations in 0002.  While I was successful, the result was awkward,
complicated, and nigh unreadable.  This is now the second time I've
attempted to fix this and have felt the result was worse than where I
started.  So, I added extremely descriptive comments instead.  I'm hoping
that it will be possible to clean this up with some additional work in v19.
I have a few ideas, but if anyone has suggestions, I'm all ears.

-- 
nathan


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-04 19:56                                                       ` Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-04 19:56 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Thu, Apr 03, 2025 at 09:19:51PM -0500, Nathan Bossart wrote:
> Great.  I'm planning to commit the attached patch set tomorrow morning.

Committed.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-04 20:06                                                         ` Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-04 20:06 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, Apr 04, 2025 at 02:56:54PM -0500, Nathan Bossart wrote:
> Committed.

I see the buildfarm failure and am working on a fix.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-04 20:58                                                           ` Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-04 20:58 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, Apr 04, 2025 at 03:06:45PM -0500, Nathan Bossart wrote:
> I see the buildfarm failure and am working on a fix.

I pushed commit 8ec0aae to fix this.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-04 22:25                                                             ` Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-04 22:25 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, Apr 04, 2025 at 03:58:53PM -0500, Nathan Bossart wrote:
> I pushed commit 8ec0aae to fix this.

And now I'm seeing cross-version test failures due to our use of WITH
ORDINALITY, which wasn't added until v9.4.  Looking into it...

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-05 02:06                                                               ` Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-04-05 02:06 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, Apr 04, 2025 at 07:32:48PM -0400, Corey Huinker wrote:
> This patch shrinks the array size to 1 for versions < 9.4, which keeps the
> modern code fairly elegant.

Committed.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-14 00:01                                                                 ` Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Hari Krishna Sunder @ 2025-05-14 00:01 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

We found a minor issue when testing statistics import with upgrading from
versions older than v14. (We have VACUUM and ANALYZE disabled)
3d351d916b20534f973eda760cde17d96545d4c4
<https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=3d351d916b20534f973eda760cde17d96545d...;
changed
the default value for reltuples from 0 to -1. So when such tables are
imported they get the pg13 default of 0 which in pg18 is treated
as "vacuumed and seen to be empty" instead of "never yet vacuumed". The
planner then proceeds to pick seq scans even if there are indexes for these
tables.
This is a very narrow edge case and the next VACUUM or ANALYZE will fix it
but the perf of these tables immediately after the upgrade is considerably
affected.

Can we instead use -1 if the version is older than 14, and reltuples is 0?
This will have the unintended consequence of treating a truly empty table
as "never yet vacuumed", but that should be fine as empty tables are going
to be fast regardless of the plan picked.

PS: This is my first patch, so apologies for any issues with the patch.


On Fri, Apr 4, 2025 at 7:06 PM Nathan Bossart <[email protected]>
wrote:

> On Fri, Apr 04, 2025 at 07:32:48PM -0400, Corey Huinker wrote:
> > This patch shrinks the array size to 1 for versions < 9.4, which keeps
> the
> > modern code fairly elegant.
>
> Committed.
>
> --
> nathan
>
>
>


Attachments:

  [application/octet-stream] 0001-Stats-import-Fix-default-reltuples-on-versions-older.patch (1.2K, ../../CAAeiqZ0o2p4SX5_xPcuAbbsmXjg6MJLNuPYSLUjC=Wh-VeW64A@mail.gmail.com/3-0001-Stats-import-Fix-default-reltuples-on-versions-older.patch)
  download | inline diff:
From 043ff3784e19c62615a8faff3ae65966cb83e557 Mon Sep 17 00:00:00 2001
From: Hari Krishna Sunder <[email protected]>
Date: Tue, 13 May 2025 23:26:32 +0000
Subject: [PATCH] Stats import: Fix default reltuples on versions older than 14

---
 src/bin/pg_dump/pg_dump.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e2e7975b34e..e75f3ca4cab 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10924,7 +10924,10 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te)
 	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
 	appendPQExpBufferStr(out, ",\n");
 	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+	if (fout->remoteVersion < 140000 && strcmp("0", rsinfo->reltuples) == 0)
+		appendPQExpBufferStr(out, "\t'reltuples', '-1'::real,\n");
+	else
+		appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
 	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
 					  rsinfo->relallvisible);
 
-- 
2.26.0



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
@ 2025-05-14 15:53                                                                   ` Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-05-14 15:53 UTC (permalink / raw)
  To: Hari Krishna Sunder <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Tue, May 13, 2025 at 05:01:02PM -0700, Hari Krishna Sunder wrote:
> We found a minor issue when testing statistics import with upgrading from
> versions older than v14. (We have VACUUM and ANALYZE disabled)
> 3d351d916b20534f973eda760cde17d96545d4c4
> <https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=3d351d916b20534f973eda760cde17d96545d...;
> changed
> the default value for reltuples from 0 to -1. So when such tables are
> imported they get the pg13 default of 0 which in pg18 is treated
> as "vacuumed and seen to be empty" instead of "never yet vacuumed". The
> planner then proceeds to pick seq scans even if there are indexes for these
> tables.
> This is a very narrow edge case and the next VACUUM or ANALYZE will fix it
> but the perf of these tables immediately after the upgrade is considerably
> affected.

There was a similar report for vacuumdb's new --missing-stats-only option.
We fixed that in commit 9879105 by removing the check for reltuples != 0,
which means that --missing-stats-only will process empty tables.

> Can we instead use -1 if the version is older than 14, and reltuples is 0?
> This will have the unintended consequence of treating a truly empty table
> as "never yet vacuumed", but that should be fine as empty tables are going
> to be fast regardless of the plan picked.

I'm inclined to agree that we should do this.  Even if it's much more
likely that 0 means empty versus not-yet-processed, the one-time cost of
processing some empty tables doesn't sound too bad.  In any case, since
this only applies to upgrades from <v14, that trade-off should dissipate
over time.

> PS: This is my first patch, so apologies for any issues with the patch.

It needs a comment, but otherwise it looks generally reasonable to me after
a quick glance.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-14 20:30                                                                     ` Hari Krishna Sunder <[email protected]>
  2025-05-16 18:47                                                                       ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Hari Krishna Sunder @ 2025-05-14 20:30 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Thanks Nathan.
Here is the patch with a comment.

On Wed, May 14, 2025 at 8:53 AM Nathan Bossart <[email protected]>
wrote:

> On Tue, May 13, 2025 at 05:01:02PM -0700, Hari Krishna Sunder wrote:
> > We found a minor issue when testing statistics import with upgrading from
> > versions older than v14. (We have VACUUM and ANALYZE disabled)
> > 3d351d916b20534f973eda760cde17d96545d4c4
> > <
> https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=3d351d916b20534f973eda760cde17d96545d...
> >
> > changed
> > the default value for reltuples from 0 to -1. So when such tables are
> > imported they get the pg13 default of 0 which in pg18 is treated
> > as "vacuumed and seen to be empty" instead of "never yet vacuumed". The
> > planner then proceeds to pick seq scans even if there are indexes for
> these
> > tables.
> > This is a very narrow edge case and the next VACUUM or ANALYZE will fix
> it
> > but the perf of these tables immediately after the upgrade is
> considerably
> > affected.
>
> There was a similar report for vacuumdb's new --missing-stats-only option.
> We fixed that in commit 9879105 by removing the check for reltuples != 0,
> which means that --missing-stats-only will process empty tables.
>
> > Can we instead use -1 if the version is older than 14, and reltuples is
> 0?
> > This will have the unintended consequence of treating a truly empty table
> > as "never yet vacuumed", but that should be fine as empty tables are
> going
> > to be fast regardless of the plan picked.
>
> I'm inclined to agree that we should do this.  Even if it's much more
> likely that 0 means empty versus not-yet-processed, the one-time cost of
> processing some empty tables doesn't sound too bad.  In any case, since
> this only applies to upgrades from <v14, that trade-off should dissipate
> over time.
>
> > PS: This is my first patch, so apologies for any issues with the patch.
>
> It needs a comment, but otherwise it looks generally reasonable to me after
> a quick glance.
>
> --
> nathan
>


Attachments:

  [application/octet-stream] 0001-Stats-import-Fix-default-reltuples-on-versions-older.patch (1.4K, ../../CAAeiqZ3BPCXziob2-Ldf15h0eS-0C6qbNoT3n5jiXEvMrjEW-w@mail.gmail.com/3-0001-Stats-import-Fix-default-reltuples-on-versions-older.patch)
  download | inline diff:
From f3024a4b50af63f61fb91a82e7b91c98f9bf882d Mon Sep 17 00:00:00 2001
From: Hari Krishna Sunder <[email protected]>
Date: Tue, 13 May 2025 23:26:32 +0000
Subject: [PATCH] Stats import: Fix default reltuples on versions older than 14

---
 src/bin/pg_dump/pg_dump.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e2e7975b34e..45548004240 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10924,7 +10924,17 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te)
 	appendStringLiteralAH(out, rsinfo->dobj.name, fout);
 	appendPQExpBufferStr(out, ",\n");
 	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+
+	/*
+	 * Before version 14, the default value for reltuples of tables that had not yet been ANALYZED
+	 * was 0. In version 14, the default value was changed to -1. Even if the table is empty, let's
+	 * just assume it has not yet been ANALYZED and set to -1.
+	 */
+	if (fout->remoteVersion < 140000 && strcmp("0", rsinfo->reltuples) == 0)
+		appendPQExpBufferStr(out, "\t'reltuples', '-1'::real,\n");
+	else
+		appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples);
+
 	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer",
 					  rsinfo->relallvisible);
 
-- 
2.26.0



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
@ 2025-05-16 18:47                                                                       ` Hari Krishna Sunder <[email protected]>
  2025-05-19 01:52                                                                         ` Re: Statistics Import and Export Michael Paquier <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Hari Krishna Sunder @ 2025-05-16 18:47 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Gentle ping on this.

---
Hari Krishna Sunder

On Wed, May 14, 2025 at 1:30 PM Hari Krishna Sunder <[email protected]>
wrote:

> Thanks Nathan.
> Here is the patch with a comment.
>
> On Wed, May 14, 2025 at 8:53 AM Nathan Bossart <[email protected]>
> wrote:
>
>> On Tue, May 13, 2025 at 05:01:02PM -0700, Hari Krishna Sunder wrote:
>> > We found a minor issue when testing statistics import with upgrading
>> from
>> > versions older than v14. (We have VACUUM and ANALYZE disabled)
>> > 3d351d916b20534f973eda760cde17d96545d4c4
>> > <
>> https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=3d351d916b20534f973eda760cde17d96545d...
>> >
>> > changed
>> > the default value for reltuples from 0 to -1. So when such tables are
>> > imported they get the pg13 default of 0 which in pg18 is treated
>> > as "vacuumed and seen to be empty" instead of "never yet vacuumed". The
>> > planner then proceeds to pick seq scans even if there are indexes for
>> these
>> > tables.
>> > This is a very narrow edge case and the next VACUUM or ANALYZE will fix
>> it
>> > but the perf of these tables immediately after the upgrade is
>> considerably
>> > affected.
>>
>> There was a similar report for vacuumdb's new --missing-stats-only option.
>> We fixed that in commit 9879105 by removing the check for reltuples != 0,
>> which means that --missing-stats-only will process empty tables.
>>
>> > Can we instead use -1 if the version is older than 14, and reltuples is
>> 0?
>> > This will have the unintended consequence of treating a truly empty
>> table
>> > as "never yet vacuumed", but that should be fine as empty tables are
>> going
>> > to be fast regardless of the plan picked.
>>
>> I'm inclined to agree that we should do this.  Even if it's much more
>> likely that 0 means empty versus not-yet-processed, the one-time cost of
>> processing some empty tables doesn't sound too bad.  In any case, since
>> this only applies to upgrades from <v14, that trade-off should dissipate
>> over time.
>>
>> > PS: This is my first patch, so apologies for any issues with the patch.
>>
>> It needs a comment, but otherwise it looks generally reasonable to me
>> after
>> a quick glance.
>>
>> --
>> nathan
>>
>


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-16 18:47                                                                       ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
@ 2025-05-19 01:52                                                                         ` Michael Paquier <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Michael Paquier @ 2025-05-19 01:52 UTC (permalink / raw)
  To: Hari Krishna Sunder <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Fri, May 16, 2025 at 11:47:12AM -0700, Hari Krishna Sunder wrote:
> Gentle ping on this.

Most of the major PostgreSQL developers were at pgconf.dev held in
Montreal last week, explaining a reduction in the activity of the
mailing lists.

Your initial report was on Monday the 14th, with this ping being on
Friday the 16th, both happening during the conference.  I suspect that
that there was just no time for folks of this thread to be able to
provide feedback for your patch, so please be a bit more patient.

Thanks!
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
@ 2025-05-19 16:51                                                                       ` Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-05-19 16:51 UTC (permalink / raw)
  To: Hari Krishna Sunder <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Wed, May 14, 2025 at 01:30:48PM -0700, Hari Krishna Sunder wrote:
> Here is the patch with a comment.

Thanks.

> On Wed, May 14, 2025 at 8:53 AM Nathan Bossart <[email protected]>
> wrote:
>> There was a similar report for vacuumdb's new --missing-stats-only option.
>> We fixed that in commit 9879105 by removing the check for reltuples != 0,
>> which means that --missing-stats-only will process empty tables.

I'm wondering if we should revert commit 9879105 if we take this change,
which solves the --missing-stats-only problem in a different way.  My
current thinking is that we should just leave it in place, if for no other
reason than analyzing some empty tables seems unlikely to cause too much
trouble.  Thoughts?

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-19 21:13                                                                         ` Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Hari Krishna Sunder @ 2025-05-19 21:13 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Sorry didn't know about the conference.

I think it would be better to revert 9879105 since there can be a
considerable number of true empty tables that we don’t need to process.

---
Hari Krishna Sunder


On Mon, May 19, 2025 at 9:51 AM Nathan Bossart <[email protected]>
wrote:

> On Wed, May 14, 2025 at 01:30:48PM -0700, Hari Krishna Sunder wrote:
> > Here is the patch with a comment.
>
> Thanks.
>
> > On Wed, May 14, 2025 at 8:53 AM Nathan Bossart <[email protected]
> >
> > wrote:
> >> There was a similar report for vacuumdb's new --missing-stats-only
> option.
> >> We fixed that in commit 9879105 by removing the check for reltuples !=
> 0,
> >> which means that --missing-stats-only will process empty tables.
>
> I'm wondering if we should revert commit 9879105 if we take this change,
> which solves the --missing-stats-only problem in a different way.  My
> current thinking is that we should just leave it in place, if for no other
> reason than analyzing some empty tables seems unlikely to cause too much
> trouble.  Thoughts?
>
> --
> nathan
>


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
@ 2025-05-19 21:31                                                                           ` Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-05-19 21:31 UTC (permalink / raw)
  To: Hari Krishna Sunder <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Mon, May 19, 2025 at 02:13:45PM -0700, Hari Krishna Sunder wrote:
> I think it would be better to revert 9879105 since there can be a
> considerable number of true empty tables that we don´t need to process.

I'm not sure that's a use-case we really need to optimize.  Even with
100,000 empty tables, "vacuumdb --analyze-only --missing-stats-only --jobs
64" completes in ~5.5 seconds on my laptop.  Plus, even if reltuples is 0,
there might actually be rows in the table, in which case analyzing it will
produce rows in pg_statistic.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-20 17:32                                                                             ` Hari Krishna Sunder <[email protected]>
  2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Hari Krishna Sunder @ 2025-05-20 17:32 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Ah ya, forgot that reltuples are not always accurate. This sounds
reasonable to me.

On Mon, May 19, 2025 at 2:32 PM Nathan Bossart <[email protected]>
wrote:

> On Mon, May 19, 2025 at 02:13:45PM -0700, Hari Krishna Sunder wrote:
> > I think it would be better to revert 9879105 since there can be a
> > considerable number of true empty tables that we don´t need to process.
>
> I'm not sure that's a use-case we really need to optimize.  Even with
> 100,000 empty tables, "vacuumdb --analyze-only --missing-stats-only --jobs
> 64" completes in ~5.5 seconds on my laptop.  Plus, even if reltuples is 0,
> there might actually be rows in the table, in which case analyzing it will
> produce rows in pg_statistic.
>
> --
> nathan
>


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
@ 2025-05-21 16:08                                                                               ` Nathan Bossart <[email protected]>
  2025-05-21 19:33                                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-21 21:14                                                                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Nathan Bossart @ 2025-05-21 16:08 UTC (permalink / raw)
  To: Hari Krishna Sunder <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Tue, May 20, 2025 at 10:32:39AM -0700, Hari Krishna Sunder wrote:
> Ah ya, forgot that reltuples are not always accurate. This sounds
> reasonable to me.

Cool.  Here is what I have staged for commit, which I am planning to do
shortly.

-- 
nathan


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-21 19:33                                                                                 ` Hari Krishna Sunder <[email protected]>
  1 sibling, 0 replies; 87+ messages in thread

From: Hari Krishna Sunder @ 2025-05-21 19:33 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Corey Huinker <[email protected]>; Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Looks good to me.


On Wed, May 21, 2025 at 9:08 AM Nathan Bossart <[email protected]>
wrote:

> On Tue, May 20, 2025 at 10:32:39AM -0700, Hari Krishna Sunder wrote:
> > Ah ya, forgot that reltuples are not always accurate. This sounds
> > reasonable to me.
>
> Cool.  Here is what I have staged for commit, which I am planning to do
> shortly.
>
> --
> nathan
>


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-21 21:14                                                                                 ` Jeff Davis <[email protected]>
  2025-05-21 21:29                                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-05-21 21:14 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; Hari Krishna Sunder <[email protected]>; +Cc: Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Wed, 2025-05-21 at 11:08 -0500, Nathan Bossart wrote:
> On Tue, May 20, 2025 at 10:32:39AM -0700, Hari Krishna Sunder wrote:
> > Ah ya, forgot that reltuples are not always accurate. This sounds
> > reasonable to me.
> 
> Cool.  Here is what I have staged for commit, which I am planning to
> do
> shortly.

Originally, one of the reasons we added a version field during dump is
so that some future version could reinterpret stats in older dump files
during import.

This patch is using a newer version of pg_dump to interpret stats from
older versions during export. That might be fine, but it would be good
to understand where the line is between things we should reinterpret
during export vs things we should reinterpret during import.

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-21 21:14                                                                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-05-21 21:29                                                                                   ` Nathan Bossart <[email protected]>
  2025-05-21 23:11                                                                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-05-21 23:53                                                                                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Nathan Bossart @ 2025-05-21 21:29 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Hari Krishna Sunder <[email protected]>; Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Wed, May 21, 2025 at 02:14:55PM -0700, Jeff Davis wrote:
> Originally, one of the reasons we added a version field during dump is
> so that some future version could reinterpret stats in older dump files
> during import.
> 
> This patch is using a newer version of pg_dump to interpret stats from
> older versions during export. That might be fine, but it would be good
> to understand where the line is between things we should reinterpret
> during export vs things we should reinterpret during import.

I don't know precisely where that line might be, but in this case, the
dumped stats have no hope of restoring into anything older than v18 (since
the stats import functions won't exist), which is well past the point where
we started using -1 for reltuples.  If we could dump the stats from v13 and
restore them into v13, then I think there would be a reasonably strong
argument for dumping it as-is and reinterpreting as necessary during
import.  But I see no particular benefit from moving the complexity to the
import side here.

Does that seem like a reasonable position to you?  Is there anything else
we should consider?

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-21 21:14                                                                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-21 21:29                                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-21 23:11                                                                                     ` Corey Huinker <[email protected]>
  1 sibling, 0 replies; 87+ messages in thread

From: Corey Huinker @ 2025-05-21 23:11 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Jeff Davis <[email protected]>; Hari Krishna Sunder <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> I don't know precisely where that line might be, but in this case, the
> dumped stats have no hope of restoring into anything older than v18 (since
> the stats import functions won't exist), which is well past the point where
> we started using -1 for reltuples.  If we could dump the stats from v13 and
> restore them into v13, then I think there would be a reasonably strong
> argument for dumping it as-is and reinterpreting as necessary during
> import.  But I see no particular benefit from moving the complexity to the
> import side here.
>

Definitely keep complexity on the export-side.

Mapping reltuples 0 -> -1 if system version < 14 like the original patch
did makes the most sense to me. That allows vacuumdb to go back to ignoring
tables that are seemingly empty while still vacuuming the tables that had
the pre-14 suspicious 0 reltuples value.



>
> Does that seem like a reasonable position to you?  Is there anything else
> we should consider?
>

Automatically vacuuming tables that purport to be empty may not take much
time, but it may alarm users using --missing-only, wondering why so many
tables didn't get stats imported, especially if we introduce a --dry-run
parameter which would answer for a user the question "what tables does
vacuumdb think are missing statistics?".


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-21 21:14                                                                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-21 21:29                                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-21 23:53                                                                                     ` Jeff Davis <[email protected]>
  2025-05-22 15:25                                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-05-21 23:53 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Hari Krishna Sunder <[email protected]>; Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Wed, 2025-05-21 at 16:29 -0500, Nathan Bossart wrote:
> I don't know precisely where that line might be, but in this case,
> the
> dumped stats have no hope of restoring into anything older than
> v18... But I see no particular benefit from moving the complexity
> to the
> import side here.

That's fine with me. Perhaps we should just say that pre-18 behavior
differences can be fixed up during export, and post-18 behavior
differences are fixed up during import?

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-21 21:14                                                                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-21 21:29                                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-21 23:53                                                                                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-05-22 15:25                                                                                       ` Nathan Bossart <[email protected]>
  2025-05-22 19:25                                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-05-22 15:25 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Hari Krishna Sunder <[email protected]>; Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Wed, May 21, 2025 at 04:53:17PM -0700, Jeff Davis wrote:
> On Wed, 2025-05-21 at 16:29 -0500, Nathan Bossart wrote:
>> I don't know precisely where that line might be, but in this case,
>> the
>> dumped stats have no hope of restoring into anything older than
>> v18... But I see no particular benefit from moving the complexity
>> to the
>> import side here.
> 
> That's fine with me. Perhaps we should just say that pre-18 behavior
> differences can be fixed up during export, and post-18 behavior
> differences are fixed up during import?

WFM.  I've committed the patch.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
  2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-21 21:14                                                                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-21 21:29                                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-21 23:53                                                                                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-22 15:25                                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-22 19:25                                                                                         ` Hari Krishna Sunder <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Hari Krishna Sunder @ 2025-05-22 19:25 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Jeff Davis <[email protected]>; Corey Huinker <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Thanks for the help. This has unblocked us!

On Thu, May 22, 2025 at 8:25 AM Nathan Bossart <[email protected]>
wrote:

> On Wed, May 21, 2025 at 04:53:17PM -0700, Jeff Davis wrote:
> > On Wed, 2025-05-21 at 16:29 -0500, Nathan Bossart wrote:
> >> I don't know precisely where that line might be, but in this case,
> >> the
> >> dumped stats have no hope of restoring into anything older than
> >> v18... But I see no particular benefit from moving the complexity
> >> to the
> >> import side here.
> >
> > That's fine with me. Perhaps we should just say that pre-18 behavior
> > differences can be fixed up during export, and post-18 behavior
> > differences are fixed up during import?
>
> WFM.  I've committed the patch.
>
> --
> nathan
>


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-04-03 02:38                                               ` Nathan Bossart <[email protected]>
  1 sibling, 0 replies; 87+ messages in thread

From: Nathan Bossart @ 2025-04-03 02:38 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Wed, Apr 02, 2025 at 10:34:58PM -0400, Corey Huinker wrote:
>>
>> > Also, why do we need the clause "WHERE s.tablename = ANY($2)"? Isn't
>> > that already implied by "JOIN unnest($1, $2) ... s.tablename =
>> > u.tablename"?
>>
>> Good question.  Corey, do you recall why this was needed?
>>
> 
> In my patch, that SQL statement came with the comment:
> 
> + /*
> + * The results must be in the order of relations supplied in the
> + * parameters to ensure that they are in sync with a walk of the TOC.
> + *
> + * The redundant (and incomplete) filter clause on s.tablename = ANY(...)
> + * is a way to lead the query into using the index
> + * pg_class_relname_nsp_index which in turn allows the planner to avoid an
> + * expensive full scan of pg_stats.
> + *
> + * We may need to adjust this query for versions that are not so easily
> + * led.
> + */

Thanks.  I included that in the latest patch set.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-16 20:33                 ` Nathan Bossart <[email protected]>
  2025-03-16 21:32                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-03-16 20:33 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

Thanks for working on this, Corey.

On Fri, Mar 14, 2025 at 04:03:16PM -0400, Corey Huinker wrote:
> 0003 -
> 
> Storing the restore function calls in the archive entry hogged a lot of
> memory and made people nervous. This introduces a new function pointer that
> generates those restore SQL calls right before they're written to disk,
> thus reducing the memory load from "stats for every object to be dumped" to
> just one object. Thanks to Nathan for diagnosing some weird quirks with
> various formats.
> 
> 0004 -
> 
> This replaces the query in the prepared statement with one that batches
> them 100 relations at a time, and then maintains that result set until it
> is consumed. It seems to have obvious speedups.

I've been doing a variety of tests with my toy database of 100K relations
[0], and I'm seeing around 20% less memory usage.  That's still 20% more
than without stats, but that's still a pretty nice improvement.

I'd propose two small changes to the design:

* I tested a variety of batch sizes, and to my suprise, I saw the best
  results with around 64 relations per batch.  I imagine the absolute best
  batch size will vary greatly depending on the workload.  It might also
  depend on work_mem and friends.

* The custom format actually does two WriteToc() calls, and since these
  patches move the queries to this part of pg_dump, it means we'll run all
  the queries twice.  The comments around this code suggest that the second
  pass isn't strictly necessary and that it is really only useful for
  data/parallel restore, so we could probably skip it for no-data dumps.

With those two changes, a pg_upgrade-style dump of my test database goes
from ~21.6 seconds without these patches to ~11.2 seconds with them.  For
reference, the same dump without stats takes ~7 seconds on HEAD.

[0] https://postgr.es/m/Z9R9-mFbxukqKmg4%40nathan

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 20:33                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-03-16 21:32                   ` Corey Huinker <[email protected]>
  2025-03-17 14:23                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-16 21:32 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

>
> * The custom format actually does two WriteToc() calls, and since these
>   patches move the queries to this part of pg_dump, it means we'll run all
>   the queries twice.  The comments around this code suggest that the second
>   pass isn't strictly necessary and that it is really only useful for
>   data/parallel restore, so we could probably skip it for no-data dumps.
>

Is there any reason we couldn't have stats objects remove themselves from
the list after completion?


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 20:33                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-03-16 21:32                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-17 14:23                     ` Nathan Bossart <[email protected]>
  2025-03-17 23:24                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-03-17 14:23 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Sun, Mar 16, 2025 at 05:32:15PM -0400, Corey Huinker wrote:
>>
>> * The custom format actually does two WriteToc() calls, and since these
>>   patches move the queries to this part of pg_dump, it means we'll run all
>>   the queries twice.  The comments around this code suggest that the second
>>   pass isn't strictly necessary and that it is really only useful for
>>   data/parallel restore, so we could probably skip it for no-data dumps.
>>
> 
> Is there any reason we couldn't have stats objects remove themselves from
> the list after completion?

I'm assuming that writing a completely different TOC on the second pass
would corrupt the dump file.  Perhaps we could teach it to skip stats
entries on the second pass or something, but I'm not too wild about adding
to the list of invasive changes we're making last-minute for v18.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 20:33                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-03-16 21:32                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-17 14:23                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-03-17 23:24                       ` Corey Huinker <[email protected]>
  2025-03-18 01:01                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Corey Huinker @ 2025-03-17 23:24 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Mon, Mar 17, 2025 at 10:24 AM Nathan Bossart <[email protected]>
wrote:

> On Sun, Mar 16, 2025 at 05:32:15PM -0400, Corey Huinker wrote:
> >>
> >> * The custom format actually does two WriteToc() calls, and since these
> >>   patches move the queries to this part of pg_dump, it means we'll run
> all
> >>   the queries twice.  The comments around this code suggest that the
> second
> >>   pass isn't strictly necessary and that it is really only useful for
> >>   data/parallel restore, so we could probably skip it for no-data dumps.
> >>
> >
> > Is there any reason we couldn't have stats objects remove themselves from
> > the list after completion?
>
> I'm assuming that writing a completely different TOC on the second pass
> would corrupt the dump file.  Perhaps we could teach it to skip stats
> entries on the second pass or something, but I'm not too wild about adding
> to the list of invasive changes we're making last-minute for v18.


I'm confused, are they needed in both places? If so, would it make sense to
write out each stat entry to a file and then re-read the file on the second
pass, or maybe do a \i filename in the sql script?

Not suggesting we do any of this for v18, but when I hear about doing
something twice when that thing was painful the first time, I look for ways
to avoid doing it, or set pan_is_hot = true for the next person.


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
  2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-16 20:33                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-03-16 21:32                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
  2025-03-17 14:23                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-03-17 23:24                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
@ 2025-03-18 01:01                         ` Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Nathan Bossart @ 2025-03-18 01:01 UTC (permalink / raw)
  To: Corey Huinker <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Treat <[email protected]>; Robert Haas <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; jian he <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Ashutosh Bapat <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]

On Mon, Mar 17, 2025 at 07:24:46PM -0400, Corey Huinker wrote:
> On Mon, Mar 17, 2025 at 10:24 AM Nathan Bossart <[email protected]>
> wrote:
>> I'm assuming that writing a completely different TOC on the second pass
>> would corrupt the dump file.  Perhaps we could teach it to skip stats
>> entries on the second pass or something, but I'm not too wild about adding
>> to the list of invasive changes we're making last-minute for v18.
> 
> I'm confused, are they needed in both places?

AFAICT yes.  The second pass rewrites the TOC to udpate the data offset
information.  If we wrote a different TOC the second time around, then the
dump file would be broken, right?

		/*
		 * If possible, re-write the TOC in order to update the data offset
		 * information.  This is not essential, as pg_restore can cope in most
		 * cases without it; but it can make pg_restore significantly faster
		 * in some situations (especially parallel restore).
		 */
		if (ctx->hasSeek &&
			fseeko(AH->FH, tpos, SEEK_SET) == 0)
			WriteToc(AH);

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
@ 2025-03-31 17:39 ` Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2 siblings, 1 reply; 87+ messages in thread

From: Robert Haas @ 2025-03-31 17:39 UTC (permalink / raw)
  To: Greg Sabino Mullane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Thu, Feb 27, 2025 at 10:43 PM Greg Sabino Mullane <[email protected]> wrote:
> I know I'm coming late to this, but I would like us to rethink having statistics dumped by default.

+1. I think I said this before, but I don't think it's correct to
regard the statistics as part of the database. It's great for
pg_upgrade to preserve them, but I think doing so for a regular dump
should be opt-in.

-- 
Robert Haas
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
@ 2025-03-31 22:04   ` Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-03-31 22:04 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; Greg Sabino Mullane <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Mon, 2025-03-31 at 13:39 -0400, Robert Haas wrote:
> +1. I think I said this before, but I don't think it's correct to
> regard the statistics as part of the database. It's great for
> pg_upgrade to preserve them, but I think doing so for a regular dump
> should be opt-in.

I'm confused about the timing of this message -- we already have an
Open Item for 18 to make this decision. After commit bde2fb797a,
changing the default is a one-line change, so there's no technical
problem.

I thought the general plan was to decide during beta. Would you like to
make the decision now for some reason?

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-04-01 20:24     ` Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-04-01 20:24 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Tue, 2025-04-01 at 09:37 -0400, Robert Haas wrote:
> I don't think I was aware of the open item; I was just catching up on
> email.

I lean towards making it opt-in for pg_dump and opt-out for pg_upgrade.
But I think we should leave open the possibility for changing the
default to opt-out for pg_dump in the future.

My reasoning for pg_dump is that releasing with stats as opt-in doesn't
put us in a worse position for making it opt-out later, so long as we
have the right set of both positive and negative options. It may even
be a better position because people have time to make their scripts
future proof by using the right combination of options.

> But I also don't really see the value of waiting until beta to
> make this decision. I seriously doubt that my opinion is going to
> change. Maybe other people's will, though: I can only speak for
> myself.

I don't think the last week before feature freeze, deep in a 400-email
thread is the best way to make decisions like this. Let's at least have
a focused thread on this topic and see if we can solicit opinions from
both sides.

Also, waiting to see if the performance improvements make it in, or
waiting for beta reports, may yield some new information that could
change minds.

Mid-beta might be too long, but let's wait for the final CF to settle
and give people the chance to respond to a top-level thread?

Regards,
	Jeff Davis






^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-05-10 19:51       ` Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
  0 siblings, 1 reply; 87+ messages in thread

From: Greg Sabino Mullane @ 2025-05-10 19:51 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Tue, Apr 1, 2025 at 10:24 PM Robert Haas <[email protected]> wrote:

> On Tue, Apr 1, 2025 at 4:24 PM Jeff Davis <[email protected]> wrote:
> > On Tue, 2025-04-01 at 09:37 -0400, Robert Haas wrote:
> > > I don't think I was aware of the open item; I was just catching up on
> > > email.
> >
> > I lean towards making it opt-in for pg_dump and opt-out for pg_upgrade.
>
> Big +1.
>

I may have missed something (we seem to have a lot of threads for this
subject), but we are in beta and both pg_dump and pg_upgrade seem to be
opt-out? I still object strongly to this;  pg_dump is meant to be a
canonical representation of the schema and data. Adding metadata that can
change from dump to dump seems wrong, and should be opt-in. I've not been
convinced otherwise why stats should be output by default.

To be clear, I 100% want it to be the default for pg_upgrade.

Maybe we are just leaving it enabled to see if anyone complains in beta,
but I don't want us to forget about it. :)

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
@ 2025-05-22 14:20         ` Robert Haas <[email protected]>
  2025-05-22 14:30           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-22 18:52           ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Robert Haas @ 2025-05-22 14:20 UTC (permalink / raw)
  To: Greg Sabino Mullane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Sat, May 10, 2025 at 3:51 PM Greg Sabino Mullane <[email protected]> wrote:
> I may have missed something (we seem to have a lot of threads for this subject), but we are in beta and both pg_dump and pg_upgrade seem to be opt-out? I still object strongly to this;  pg_dump is meant to be a canonical representation of the schema and data. Adding metadata that can change from dump to dump seems wrong, and should be opt-in. I've not been convinced otherwise why stats should be output by default.
>
> To be clear, I 100% want it to be the default for pg_upgrade.
>
> Maybe we are just leaving it enabled to see if anyone complains in beta, but I don't want us to forget about it. :)

Yeah. This could use comments from a few more people, but I really
hope we don't ship the final release this way. We do have a "Enable
statistics in pg_dump by default" item in the open items list under
"Decisions to Recheck Mid-Beta", but that's arguably now. It also sort
of looks like we might have a consensus anyway: Jeff said "I lean
towards making it opt-in for pg_dump and opt-out for pg_upgrade" and I
agree with that and it seems you do, too. So perhaps Jeff should make
it so?

-- 
Robert Haas
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
@ 2025-05-22 14:30           ` Nathan Bossart <[email protected]>
  2025-05-22 14:53             ` Re: Statistics Import and Export Tom Lane <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-05-22 14:30 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Thu, May 22, 2025 at 10:20:16AM -0400, Robert Haas wrote:
> It also sort
> of looks like we might have a consensus anyway: Jeff said "I lean
> towards making it opt-in for pg_dump and opt-out for pg_upgrade" and I
> agree with that and it seems you do, too. So perhaps Jeff should make
> it so?

+1, I think we should go ahead and do this.  If Jeff can't get to it, I'm
happy to pick it up in the next week or so.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-05-22 14:30           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-22 14:53             ` Tom Lane <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Tom Lane @ 2025-05-22 14:53 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Robert Haas <[email protected]>; Greg Sabino Mullane <[email protected]>; Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

Nathan Bossart <[email protected]> writes:
> On Thu, May 22, 2025 at 10:20:16AM -0400, Robert Haas wrote:
>> It also sort
>> of looks like we might have a consensus anyway: Jeff said "I lean
>> towards making it opt-in for pg_dump and opt-out for pg_upgrade" and I
>> agree with that and it seems you do, too. So perhaps Jeff should make
>> it so?

> +1, I think we should go ahead and do this.  If Jeff can't get to it, I'm
> happy to pick it up in the next week or so.

Works for me, too.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
@ 2025-05-22 18:52           ` Jeff Davis <[email protected]>
  2025-05-22 19:29             ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Jeff Davis @ 2025-05-22 18:52 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; Greg Sabino Mullane <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Thu, 2025-05-22 at 10:20 -0400, Robert Haas wrote:
> Yeah. This could use comments from a few more people, but I really
> hope we don't ship the final release this way. We do have a "Enable
> statistics in pg_dump by default" item in the open items list under
> "Decisions to Recheck Mid-Beta", but that's arguably now. It also
> sort
> of looks like we might have a consensus anyway: Jeff said "I lean
> towards making it opt-in for pg_dump and opt-out for pg_upgrade" and
> I
> agree with that and it seems you do, too. So perhaps Jeff should make
> it so?

Patch attached.

A couple minor points:

 * The default for pg_restore is --no-statistics. That could cause a
minor surprise if the user specifies --with-statistics for pg_dump and
not for pg_restore. An argument could be made that "if the stats are
there, restore them", and I don't have a strong opinion about this
point, but defaulting to --no-statistics seems more consistent with
pg_dump.

 * I added --with-statistics to most of the pg_dump tests. We can be
more judicious about which tests exercise statistics as a separate
commit, but I didn't want to change the test results as a part of this
commit.

Regards,
	Jeff Davis



Attachments:

  [text/x-patch] v1-0001-Change-defaults-for-statistics-export.patch (15.1K, ../../[email protected]/2-v1-0001-Change-defaults-for-statistics-export.patch)
  download | inline diff:
From b76cb91441e2eefe278249e23fcd703d27a85a06 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 22 May 2025 11:03:03 -0700
Subject: [PATCH v1] Change defaults for statistics export.

Set the default behavior of pg_dump, pg_dumpall, and pg_restore to be
--no-statistics. Leave the default for pg_upgrade to be
--with-statistics.

Discussion: https://postgr.es/m/CA+TgmoZ9=RnWcCOZiKYYjZs_AW1P4QXCw--h4dOLLHuf1Omung@mail.gmail.com
---
 src/bin/pg_dump/pg_backup_archiver.c |  4 +-
 src/bin/pg_dump/t/002_pg_dump.pl     | 59 ++++++++++++++++++++++++++++
 src/bin/pg_upgrade/dump.c            |  2 +-
 src/bin/pg_upgrade/pg_upgrade.c      |  6 ++-
 4 files changed, 66 insertions(+), 5 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index afa42337b11..a66d88bbc51 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -152,7 +152,7 @@ InitDumpOptions(DumpOptions *opts)
 	opts->dumpSections = DUMP_UNSECTIONED;
 	opts->dumpSchema = true;
 	opts->dumpData = true;
-	opts->dumpStatistics = true;
+	opts->dumpStatistics = false;
 }
 
 /*
@@ -1101,7 +1101,7 @@ NewRestoreOptions(void)
 	opts->compression_spec.level = 0;
 	opts->dumpSchema = true;
 	opts->dumpData = true;
-	opts->dumpStatistics = true;
+	opts->dumpStatistics = false;
 
 	return opts;
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index cf34f71ea11..386e21e0c59 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -68,6 +68,7 @@ my %pgdump_runs = (
 			'--no-data',
 			'--sequence-data',
 			'--binary-upgrade',
+			'--with-statistics',
 			'--dbname' => 'postgres',    # alternative way to specify database
 		],
 		restore_cmd => [
@@ -75,6 +76,7 @@ my %pgdump_runs = (
 			'--format' => 'custom',
 			'--verbose',
 			'--file' => "$tempdir/binary_upgrade.sql",
+			'--with-statistics',
 			"$tempdir/binary_upgrade.dump",
 		],
 	},
@@ -88,11 +90,13 @@ my %pgdump_runs = (
 			'--format' => 'custom',
 			'--compress' => '1',
 			'--file' => "$tempdir/compression_gzip_custom.dump",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/compression_gzip_custom.sql",
+			'--with-statistics',
 			"$tempdir/compression_gzip_custom.dump",
 		],
 		command_like => {
@@ -115,6 +119,7 @@ my %pgdump_runs = (
 			'--format' => 'directory',
 			'--compress' => 'gzip:1',
 			'--file' => "$tempdir/compression_gzip_dir",
+			'--with-statistics',
 			'postgres',
 		],
 		# Give coverage for manually compressed blobs.toc files during
@@ -132,6 +137,7 @@ my %pgdump_runs = (
 			'pg_restore',
 			'--jobs' => '2',
 			'--file' => "$tempdir/compression_gzip_dir.sql",
+			'--with-statistics',
 			"$tempdir/compression_gzip_dir",
 		],
 	},
@@ -144,6 +150,7 @@ my %pgdump_runs = (
 			'--format' => 'plain',
 			'--compress' => '1',
 			'--file' => "$tempdir/compression_gzip_plain.sql.gz",
+			'--with-statistics',
 			'postgres',
 		],
 		# Decompress the generated file to run through the tests.
@@ -162,11 +169,13 @@ my %pgdump_runs = (
 			'--format' => 'custom',
 			'--compress' => 'lz4',
 			'--file' => "$tempdir/compression_lz4_custom.dump",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/compression_lz4_custom.sql",
+			'--with-statistics',
 			"$tempdir/compression_lz4_custom.dump",
 		],
 		command_like => {
@@ -189,6 +198,7 @@ my %pgdump_runs = (
 			'--format' => 'directory',
 			'--compress' => 'lz4:1',
 			'--file' => "$tempdir/compression_lz4_dir",
+			'--with-statistics',
 			'postgres',
 		],
 		# Verify that data files were compressed
@@ -200,6 +210,7 @@ my %pgdump_runs = (
 			'pg_restore',
 			'--jobs' => '2',
 			'--file' => "$tempdir/compression_lz4_dir.sql",
+			'--with-statistics',
 			"$tempdir/compression_lz4_dir",
 		],
 	},
@@ -212,6 +223,7 @@ my %pgdump_runs = (
 			'--format' => 'plain',
 			'--compress' => 'lz4',
 			'--file' => "$tempdir/compression_lz4_plain.sql.lz4",
+			'--with-statistics',
 			'postgres',
 		],
 		# Decompress the generated file to run through the tests.
@@ -233,11 +245,13 @@ my %pgdump_runs = (
 			'--format' => 'custom',
 			'--compress' => 'zstd',
 			'--file' => "$tempdir/compression_zstd_custom.dump",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/compression_zstd_custom.sql",
+			'--with-statistics',
 			"$tempdir/compression_zstd_custom.dump",
 		],
 		command_like => {
@@ -259,6 +273,7 @@ my %pgdump_runs = (
 			'--format' => 'directory',
 			'--compress' => 'zstd:1',
 			'--file' => "$tempdir/compression_zstd_dir",
+			'--with-statistics',
 			'postgres',
 		],
 		# Give coverage for manually compressed blobs.toc files during
@@ -279,6 +294,7 @@ my %pgdump_runs = (
 			'pg_restore',
 			'--jobs' => '2',
 			'--file' => "$tempdir/compression_zstd_dir.sql",
+			'--with-statistics',
 			"$tempdir/compression_zstd_dir",
 		],
 	},
@@ -292,6 +308,7 @@ my %pgdump_runs = (
 			'--format' => 'plain',
 			'--compress' => 'zstd:long',
 			'--file' => "$tempdir/compression_zstd_plain.sql.zst",
+			'--with-statistics',
 			'postgres',
 		],
 		# Decompress the generated file to run through the tests.
@@ -310,6 +327,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/clean.sql",
 			'--clean',
+			'--with-statistics',
 			'--dbname' => 'postgres',    # alternative way to specify database
 		],
 	},
@@ -320,6 +338,7 @@ my %pgdump_runs = (
 			'--clean',
 			'--if-exists',
 			'--encoding' => 'UTF8',      # no-op, just for testing
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -338,6 +357,7 @@ my %pgdump_runs = (
 			'--create',
 			'--no-reconnect',    # no-op, just for testing
 			'--verbose',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -356,6 +376,7 @@ my %pgdump_runs = (
 		dump_cmd => [
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/defaults.sql",
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -364,6 +385,7 @@ my %pgdump_runs = (
 		dump_cmd => [
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/defaults_no_public.sql",
+			'--with-statistics',
 			'regress_pg_dump_test',
 		],
 	},
@@ -373,6 +395,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--clean',
 			'--file' => "$tempdir/defaults_no_public_clean.sql",
+			'--with-statistics',
 			'regress_pg_dump_test',
 		],
 	},
@@ -381,6 +404,7 @@ my %pgdump_runs = (
 		dump_cmd => [
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/defaults_public_owner.sql",
+			'--with-statistics',
 			'regress_public_owner',
 		],
 	},
@@ -395,12 +419,14 @@ my %pgdump_runs = (
 			'pg_dump',
 			'--format' => 'custom',
 			'--file' => "$tempdir/defaults_custom_format.dump",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--format' => 'custom',
 			'--file' => "$tempdir/defaults_custom_format.sql",
+			'--with-statistics',
 			"$tempdir/defaults_custom_format.dump",
 		],
 		command_like => {
@@ -425,12 +451,14 @@ my %pgdump_runs = (
 			'pg_dump',
 			'--format' => 'directory',
 			'--file' => "$tempdir/defaults_dir_format",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--format' => 'directory',
 			'--file' => "$tempdir/defaults_dir_format.sql",
+			'--with-statistics',
 			"$tempdir/defaults_dir_format",
 		],
 		command_like => {
@@ -456,11 +484,13 @@ my %pgdump_runs = (
 			'--format' => 'directory',
 			'--jobs' => 2,
 			'--file' => "$tempdir/defaults_parallel",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/defaults_parallel.sql",
+			'--with-statistics',
 			"$tempdir/defaults_parallel",
 		],
 	},
@@ -472,12 +502,14 @@ my %pgdump_runs = (
 			'pg_dump',
 			'--format' => 'tar',
 			'--file' => "$tempdir/defaults_tar_format.tar",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--format' => 'tar',
 			'--file' => "$tempdir/defaults_tar_format.sql",
+			'--with-statistics',
 			"$tempdir/defaults_tar_format.tar",
 		],
 	},
@@ -486,6 +518,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/exclude_dump_test_schema.sql",
 			'--exclude-schema' => 'dump_test',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -494,6 +527,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/exclude_test_table.sql",
 			'--exclude-table' => 'dump_test.test_table',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -502,6 +536,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/exclude_measurement.sql",
 			'--exclude-table-and-children' => 'dump_test.measurement',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -511,6 +546,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/exclude_measurement_data.sql",
 			'--exclude-table-data-and-children' => 'dump_test.measurement',
 			'--no-unlogged-table-data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -520,6 +556,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/exclude_test_table_data.sql",
 			'--exclude-table-data' => 'dump_test.test_table',
 			'--no-unlogged-table-data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -538,6 +575,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/pg_dumpall_globals.sql",
 			'--globals-only',
 			'--no-sync',
+			'--with-statistics',
 		],
 	},
 	pg_dumpall_globals_clean => {
@@ -547,12 +585,14 @@ my %pgdump_runs = (
 			'--globals-only',
 			'--clean',
 			'--no-sync',
+			'--with-statistics',
 		],
 	},
 	pg_dumpall_dbprivs => {
 		dump_cmd => [
 			'pg_dumpall', '--no-sync',
 			'--file' => "$tempdir/pg_dumpall_dbprivs.sql",
+			'--with-statistics',
 		],
 	},
 	pg_dumpall_exclude => {
@@ -562,6 +602,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/pg_dumpall_exclude.sql",
 			'--exclude-database' => '*dump_test*',
 			'--no-sync',
+			'--with-statistics',
 		],
 	},
 	no_toast_compression => {
@@ -569,6 +610,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_toast_compression.sql",
 			'--no-toast-compression',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -577,6 +619,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_large_objects.sql",
 			'--no-large-objects',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -585,6 +628,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_policies.sql",
 			'--no-policies',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -593,6 +637,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_privs.sql",
 			'--no-privileges',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -601,6 +646,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_owner.sql",
 			'--no-owner',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -609,6 +655,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_table_access_method.sql",
 			'--no-table-access-method',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -617,6 +664,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/only_dump_test_schema.sql",
 			'--schema' => 'dump_test',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -627,6 +675,7 @@ my %pgdump_runs = (
 			'--table' => 'dump_test.test_table',
 			'--lock-wait-timeout' =>
 			  (1000 * $PostgreSQL::Test::Utils::timeout_default),
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -637,6 +686,7 @@ my %pgdump_runs = (
 			'--table-and-children' => 'dump_test.measurement',
 			'--lock-wait-timeout' =>
 			  (1000 * $PostgreSQL::Test::Utils::timeout_default),
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -646,6 +696,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/role.sql",
 			'--role' => 'regress_dump_test_role',
 			'--schema' => 'dump_test_second_schema',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -658,11 +709,13 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/role_parallel",
 			'--role' => 'regress_dump_test_role',
 			'--schema' => 'dump_test_second_schema',
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/role_parallel.sql",
+			'--with-statistics',
 			"$tempdir/role_parallel",
 		],
 	},
@@ -691,6 +744,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/section_pre_data.sql",
 			'--section' => 'pre-data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -699,6 +753,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/section_data.sql",
 			'--section' => 'data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -707,6 +762,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/section_post_data.sql",
 			'--section' => 'post-data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -717,6 +773,7 @@ my %pgdump_runs = (
 			'--schema' => 'dump_test',
 			'--large-objects',
 			'--no-large-objects',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -732,6 +789,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			"--file=$tempdir/no_data_no_schema.sql", '--no-data',
 			'--no-schema', 'postgres',
+			'--with-statistics',
 		],
 	},
 	statistics_only => {
@@ -752,6 +810,7 @@ my %pgdump_runs = (
 		dump_cmd => [
 			'pg_dump', '--no-sync',
 			"--file=$tempdir/no_schema.sql", '--no-schema',
+			'--with-statistics',
 			'postgres',
 		],
 	},);
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 23cb08e8347..183f08ce1e8 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -58,7 +58,7 @@ generate_old_dump(void)
 						   (user_opts.transfer_mode == TRANSFER_MODE_SWAP) ?
 						   "" : "--sequence-data",
 						   log_opts.verbose ? "--verbose" : "",
-						   user_opts.do_statistics ? "" : "--no-statistics",
+						   user_opts.do_statistics ? "--with-statistics" : "--no-statistics",
 						   log_opts.dumpdir,
 						   sql_file_name, escaped_connstr.data);
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 536e49d2616..81a394f249d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -618,12 +618,13 @@ create_new_objects(void)
 				  NULL,
 				  true,
 				  true,
-				  "\"%s/pg_restore\" %s %s --exit-on-error --verbose "
+				  "\"%s/pg_restore\" %s %s %s --exit-on-error --verbose "
 				  "--transaction-size=%d "
 				  "--dbname postgres \"%s/%s\"",
 				  new_cluster.bindir,
 				  cluster_conn_opts(&new_cluster),
 				  create_opts,
+				  user_opts.do_statistics ? "--with-statistics" : "--no-statistics",
 				  RESTORE_TRANSACTION_SIZE,
 				  log_opts.dumpdir,
 				  sql_file_name);
@@ -672,12 +673,13 @@ create_new_objects(void)
 
 		parallel_exec_prog(log_file_name,
 						   NULL,
-						   "\"%s/pg_restore\" %s %s --exit-on-error --verbose "
+						   "\"%s/pg_restore\" %s %s %s --exit-on-error --verbose "
 						   "--transaction-size=%d "
 						   "--dbname template1 \"%s/%s\"",
 						   new_cluster.bindir,
 						   cluster_conn_opts(&new_cluster),
 						   create_opts,
+						   user_opts.do_statistics ? "--with-statistics" : "--no-statistics",
 						   txn_size,
 						   log_opts.dumpdir,
 						   sql_file_name);
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-05-22 18:52           ` Re: Statistics Import and Export Jeff Davis <[email protected]>
@ 2025-05-22 19:29             ` Greg Sabino Mullane <[email protected]>
  2025-05-22 19:36               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
  2025-05-22 19:41               ` Re: Statistics Import and Export Tom Lane <[email protected]>
  0 siblings, 2 replies; 87+ messages in thread

From: Greg Sabino Mullane @ 2025-05-22 19:29 UTC (permalink / raw)
  To: Jeff Davis <[email protected]>; +Cc: Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Thu, May 22, 2025 at 2:52 PM Jeff Davis <[email protected]> wrote:

>  * The default for pg_restore is --no-statistics. That could cause a minor
> surprise if the user specifies --with-statistics for pg_dump and
> not for pg_restore. An argument could be made that "if the stats are
> there, restore them", and I don't have a strong opinion about this point,
> but defaulting to --no-statistics seems more consistent with pg_dump.
>

Hm...somewhat to my own surprise, I don't like this. If it's in the dump,
restore it.

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support


^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-05-22 18:52           ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-22 19:29             ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
@ 2025-05-22 19:36               ` Nathan Bossart <[email protected]>
  2025-05-22 19:36                 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Nathan Bossart @ 2025-05-22 19:36 UTC (permalink / raw)
  To: Greg Sabino Mullane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Thu, May 22, 2025 at 03:29:38PM -0400, Greg Sabino Mullane wrote:
> On Thu, May 22, 2025 at 2:52 PM Jeff Davis <[email protected]> wrote:
>>  * The default for pg_restore is --no-statistics. That could cause a minor
>> surprise if the user specifies --with-statistics for pg_dump and
>> not for pg_restore. An argument could be made that "if the stats are
>> there, restore them", and I don't have a strong opinion about this point,
>> but defaulting to --no-statistics seems more consistent with pg_dump.
> 
> Hm...somewhat to my own surprise, I don't like this. If it's in the dump,
> restore it.

+1, I think defaulting to restoring everything in the dump file is much
less surprising than the alternative.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-05-22 18:52           ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-22 19:29             ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 19:36               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
@ 2025-05-22 19:36                 ` Robert Haas <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Robert Haas @ 2025-05-22 19:36 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Greg Sabino Mullane <[email protected]>; Jeff Davis <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Michael Paquier <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Thu, May 22, 2025 at 3:36 PM Nathan Bossart <[email protected]> wrote:
> +1, I think defaulting to restoring everything in the dump file is much
> less surprising than the alternative.

+1.

-- 
Robert Haas
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-05-22 18:52           ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-22 19:29             ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
@ 2025-05-22 19:41               ` Tom Lane <[email protected]>
  2025-05-22 21:29                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  1 sibling, 1 reply; 87+ messages in thread

From: Tom Lane @ 2025-05-22 19:41 UTC (permalink / raw)
  To: Greg Sabino Mullane <[email protected]>; +Cc: Jeff Davis <[email protected]>; Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

Greg Sabino Mullane <[email protected]> writes:
> On Thu, May 22, 2025 at 2:52 PM Jeff Davis <[email protected]> wrote:
>> * The default for pg_restore is --no-statistics. That could cause a minor
>> surprise if the user specifies --with-statistics for pg_dump and
>> not for pg_restore.

> Hm...somewhat to my own surprise, I don't like this. If it's in the dump,
> restore it.

Yeah, I tend to lean that way too.  If the user went out of their way
to say --with-statistics for pg_dump, how likely is it that they
don't want the statistics restored?

Another argument pointing in that direction is that the definition
Jeff proposes creates an inconsistency in the output between text
mode:

	pg_dump --with-statistics ... | psql

and non-text mode:

	pg_dump -Fc --with-statistics ... | pg_restore

There is no additional filter in text mode, so I think pg_restore's
default behavior should also be "no additional filter".

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 87+ messages in thread

* Re: Statistics Import and Export
  2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
  2025-05-22 18:52           ` Re: Statistics Import and Export Jeff Davis <[email protected]>
  2025-05-22 19:29             ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
  2025-05-22 19:41               ` Re: Statistics Import and Export Tom Lane <[email protected]>
@ 2025-05-22 21:29                 ` Jeff Davis <[email protected]>
  0 siblings, 0 replies; 87+ messages in thread

From: Jeff Davis @ 2025-05-22 21:29 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; Greg Sabino Mullane <[email protected]>; +Cc: Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; Corey Huinker <[email protected]>; Andres Freund <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Bruce Momjian <[email protected]>; Matthias van de Meent <[email protected]>; Magnus Hagander <[email protected]>; Stephen Frost <[email protected]>; Peter Smith <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; jian he <[email protected]>

On Thu, 2025-05-22 at 15:41 -0400, Tom Lane wrote:
> There is no additional filter in text mode, so I think pg_restore's
> default behavior should also be "no additional filter".

Attached. Only the defaults for pg_dump and pg_dumpall are changed, and
pg_upgrade explicitly specifies --with-statistics.

Regards,
	Jeff Davis



Attachments:

  [text/x-patch] v2-0001-Change-defaults-for-statistics-export.patch (15.3K, ../../[email protected]/2-v2-0001-Change-defaults-for-statistics-export.patch)
  download | inline diff:
From 5b73253f8848638f1754f4b9da82e90e8814b4b1 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 22 May 2025 11:03:03 -0700
Subject: [PATCH v2] Change defaults for statistics export.

Set the default behavior of pg_dump, pg_dumpall, and pg_restore to be
--no-statistics. Leave the default for pg_upgrade to be
--with-statistics.

Discussion: https://postgr.es/m/CA+TgmoZ9=RnWcCOZiKYYjZs_AW1P4QXCw--h4dOLLHuf1Omung@mail.gmail.com
---
 doc/src/sgml/ref/pg_dump.sgml        |  4 +-
 doc/src/sgml/ref/pg_dumpall.sgml     |  4 +-
 src/bin/pg_dump/pg_backup_archiver.c |  2 +-
 src/bin/pg_dump/t/002_pg_dump.pl     | 59 ++++++++++++++++++++++++++++
 src/bin/pg_upgrade/dump.c            |  2 +-
 5 files changed, 65 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index c10bca63e55..995d8f9a040 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1134,7 +1134,7 @@ PostgreSQL documentation
       <term><option>--no-statistics</option></term>
       <listitem>
        <para>
-        Do not dump statistics.
+        Do not dump statistics. This is the default.
        </para>
       </listitem>
      </varlistentry>
@@ -1461,7 +1461,7 @@ PostgreSQL documentation
       <term><option>--with-statistics</option></term>
       <listitem>
        <para>
-        Dump statistics. This is the default.
+        Dump statistics.
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index 8c5141d036c..81d34df3386 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -567,7 +567,7 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
       <term><option>--no-statistics</option></term>
       <listitem>
        <para>
-        Do not dump statistics.
+        Do not dump statistics. This is the default.
        </para>
       </listitem>
      </varlistentry>
@@ -741,7 +741,7 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
       <term><option>--with-statistics</option></term>
       <listitem>
        <para>
-        Dump statistics. This is the default.
+        Dump statistics.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index afa42337b11..175fe9c4273 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -152,7 +152,7 @@ InitDumpOptions(DumpOptions *opts)
 	opts->dumpSections = DUMP_UNSECTIONED;
 	opts->dumpSchema = true;
 	opts->dumpData = true;
-	opts->dumpStatistics = true;
+	opts->dumpStatistics = false;
 }
 
 /*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index cf34f71ea11..386e21e0c59 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -68,6 +68,7 @@ my %pgdump_runs = (
 			'--no-data',
 			'--sequence-data',
 			'--binary-upgrade',
+			'--with-statistics',
 			'--dbname' => 'postgres',    # alternative way to specify database
 		],
 		restore_cmd => [
@@ -75,6 +76,7 @@ my %pgdump_runs = (
 			'--format' => 'custom',
 			'--verbose',
 			'--file' => "$tempdir/binary_upgrade.sql",
+			'--with-statistics',
 			"$tempdir/binary_upgrade.dump",
 		],
 	},
@@ -88,11 +90,13 @@ my %pgdump_runs = (
 			'--format' => 'custom',
 			'--compress' => '1',
 			'--file' => "$tempdir/compression_gzip_custom.dump",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/compression_gzip_custom.sql",
+			'--with-statistics',
 			"$tempdir/compression_gzip_custom.dump",
 		],
 		command_like => {
@@ -115,6 +119,7 @@ my %pgdump_runs = (
 			'--format' => 'directory',
 			'--compress' => 'gzip:1',
 			'--file' => "$tempdir/compression_gzip_dir",
+			'--with-statistics',
 			'postgres',
 		],
 		# Give coverage for manually compressed blobs.toc files during
@@ -132,6 +137,7 @@ my %pgdump_runs = (
 			'pg_restore',
 			'--jobs' => '2',
 			'--file' => "$tempdir/compression_gzip_dir.sql",
+			'--with-statistics',
 			"$tempdir/compression_gzip_dir",
 		],
 	},
@@ -144,6 +150,7 @@ my %pgdump_runs = (
 			'--format' => 'plain',
 			'--compress' => '1',
 			'--file' => "$tempdir/compression_gzip_plain.sql.gz",
+			'--with-statistics',
 			'postgres',
 		],
 		# Decompress the generated file to run through the tests.
@@ -162,11 +169,13 @@ my %pgdump_runs = (
 			'--format' => 'custom',
 			'--compress' => 'lz4',
 			'--file' => "$tempdir/compression_lz4_custom.dump",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/compression_lz4_custom.sql",
+			'--with-statistics',
 			"$tempdir/compression_lz4_custom.dump",
 		],
 		command_like => {
@@ -189,6 +198,7 @@ my %pgdump_runs = (
 			'--format' => 'directory',
 			'--compress' => 'lz4:1',
 			'--file' => "$tempdir/compression_lz4_dir",
+			'--with-statistics',
 			'postgres',
 		],
 		# Verify that data files were compressed
@@ -200,6 +210,7 @@ my %pgdump_runs = (
 			'pg_restore',
 			'--jobs' => '2',
 			'--file' => "$tempdir/compression_lz4_dir.sql",
+			'--with-statistics',
 			"$tempdir/compression_lz4_dir",
 		],
 	},
@@ -212,6 +223,7 @@ my %pgdump_runs = (
 			'--format' => 'plain',
 			'--compress' => 'lz4',
 			'--file' => "$tempdir/compression_lz4_plain.sql.lz4",
+			'--with-statistics',
 			'postgres',
 		],
 		# Decompress the generated file to run through the tests.
@@ -233,11 +245,13 @@ my %pgdump_runs = (
 			'--format' => 'custom',
 			'--compress' => 'zstd',
 			'--file' => "$tempdir/compression_zstd_custom.dump",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/compression_zstd_custom.sql",
+			'--with-statistics',
 			"$tempdir/compression_zstd_custom.dump",
 		],
 		command_like => {
@@ -259,6 +273,7 @@ my %pgdump_runs = (
 			'--format' => 'directory',
 			'--compress' => 'zstd:1',
 			'--file' => "$tempdir/compression_zstd_dir",
+			'--with-statistics',
 			'postgres',
 		],
 		# Give coverage for manually compressed blobs.toc files during
@@ -279,6 +294,7 @@ my %pgdump_runs = (
 			'pg_restore',
 			'--jobs' => '2',
 			'--file' => "$tempdir/compression_zstd_dir.sql",
+			'--with-statistics',
 			"$tempdir/compression_zstd_dir",
 		],
 	},
@@ -292,6 +308,7 @@ my %pgdump_runs = (
 			'--format' => 'plain',
 			'--compress' => 'zstd:long',
 			'--file' => "$tempdir/compression_zstd_plain.sql.zst",
+			'--with-statistics',
 			'postgres',
 		],
 		# Decompress the generated file to run through the tests.
@@ -310,6 +327,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/clean.sql",
 			'--clean',
+			'--with-statistics',
 			'--dbname' => 'postgres',    # alternative way to specify database
 		],
 	},
@@ -320,6 +338,7 @@ my %pgdump_runs = (
 			'--clean',
 			'--if-exists',
 			'--encoding' => 'UTF8',      # no-op, just for testing
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -338,6 +357,7 @@ my %pgdump_runs = (
 			'--create',
 			'--no-reconnect',    # no-op, just for testing
 			'--verbose',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -356,6 +376,7 @@ my %pgdump_runs = (
 		dump_cmd => [
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/defaults.sql",
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -364,6 +385,7 @@ my %pgdump_runs = (
 		dump_cmd => [
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/defaults_no_public.sql",
+			'--with-statistics',
 			'regress_pg_dump_test',
 		],
 	},
@@ -373,6 +395,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--clean',
 			'--file' => "$tempdir/defaults_no_public_clean.sql",
+			'--with-statistics',
 			'regress_pg_dump_test',
 		],
 	},
@@ -381,6 +404,7 @@ my %pgdump_runs = (
 		dump_cmd => [
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/defaults_public_owner.sql",
+			'--with-statistics',
 			'regress_public_owner',
 		],
 	},
@@ -395,12 +419,14 @@ my %pgdump_runs = (
 			'pg_dump',
 			'--format' => 'custom',
 			'--file' => "$tempdir/defaults_custom_format.dump",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--format' => 'custom',
 			'--file' => "$tempdir/defaults_custom_format.sql",
+			'--with-statistics',
 			"$tempdir/defaults_custom_format.dump",
 		],
 		command_like => {
@@ -425,12 +451,14 @@ my %pgdump_runs = (
 			'pg_dump',
 			'--format' => 'directory',
 			'--file' => "$tempdir/defaults_dir_format",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--format' => 'directory',
 			'--file' => "$tempdir/defaults_dir_format.sql",
+			'--with-statistics',
 			"$tempdir/defaults_dir_format",
 		],
 		command_like => {
@@ -456,11 +484,13 @@ my %pgdump_runs = (
 			'--format' => 'directory',
 			'--jobs' => 2,
 			'--file' => "$tempdir/defaults_parallel",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/defaults_parallel.sql",
+			'--with-statistics',
 			"$tempdir/defaults_parallel",
 		],
 	},
@@ -472,12 +502,14 @@ my %pgdump_runs = (
 			'pg_dump',
 			'--format' => 'tar',
 			'--file' => "$tempdir/defaults_tar_format.tar",
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--format' => 'tar',
 			'--file' => "$tempdir/defaults_tar_format.sql",
+			'--with-statistics',
 			"$tempdir/defaults_tar_format.tar",
 		],
 	},
@@ -486,6 +518,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/exclude_dump_test_schema.sql",
 			'--exclude-schema' => 'dump_test',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -494,6 +527,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/exclude_test_table.sql",
 			'--exclude-table' => 'dump_test.test_table',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -502,6 +536,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/exclude_measurement.sql",
 			'--exclude-table-and-children' => 'dump_test.measurement',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -511,6 +546,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/exclude_measurement_data.sql",
 			'--exclude-table-data-and-children' => 'dump_test.measurement',
 			'--no-unlogged-table-data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -520,6 +556,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/exclude_test_table_data.sql",
 			'--exclude-table-data' => 'dump_test.test_table',
 			'--no-unlogged-table-data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -538,6 +575,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/pg_dumpall_globals.sql",
 			'--globals-only',
 			'--no-sync',
+			'--with-statistics',
 		],
 	},
 	pg_dumpall_globals_clean => {
@@ -547,12 +585,14 @@ my %pgdump_runs = (
 			'--globals-only',
 			'--clean',
 			'--no-sync',
+			'--with-statistics',
 		],
 	},
 	pg_dumpall_dbprivs => {
 		dump_cmd => [
 			'pg_dumpall', '--no-sync',
 			'--file' => "$tempdir/pg_dumpall_dbprivs.sql",
+			'--with-statistics',
 		],
 	},
 	pg_dumpall_exclude => {
@@ -562,6 +602,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/pg_dumpall_exclude.sql",
 			'--exclude-database' => '*dump_test*',
 			'--no-sync',
+			'--with-statistics',
 		],
 	},
 	no_toast_compression => {
@@ -569,6 +610,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_toast_compression.sql",
 			'--no-toast-compression',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -577,6 +619,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_large_objects.sql",
 			'--no-large-objects',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -585,6 +628,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_policies.sql",
 			'--no-policies',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -593,6 +637,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_privs.sql",
 			'--no-privileges',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -601,6 +646,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_owner.sql",
 			'--no-owner',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -609,6 +655,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/no_table_access_method.sql",
 			'--no-table-access-method',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -617,6 +664,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/only_dump_test_schema.sql",
 			'--schema' => 'dump_test',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -627,6 +675,7 @@ my %pgdump_runs = (
 			'--table' => 'dump_test.test_table',
 			'--lock-wait-timeout' =>
 			  (1000 * $PostgreSQL::Test::Utils::timeout_default),
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -637,6 +686,7 @@ my %pgdump_runs = (
 			'--table-and-children' => 'dump_test.measurement',
 			'--lock-wait-timeout' =>
 			  (1000 * $PostgreSQL::Test::Utils::timeout_default),
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -646,6 +696,7 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/role.sql",
 			'--role' => 'regress_dump_test_role',
 			'--schema' => 'dump_test_second_schema',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -658,11 +709,13 @@ my %pgdump_runs = (
 			'--file' => "$tempdir/role_parallel",
 			'--role' => 'regress_dump_test_role',
 			'--schema' => 'dump_test_second_schema',
+			'--with-statistics',
 			'postgres',
 		],
 		restore_cmd => [
 			'pg_restore',
 			'--file' => "$tempdir/role_parallel.sql",
+			'--with-statistics',
 			"$tempdir/role_parallel",
 		],
 	},
@@ -691,6 +744,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/section_pre_data.sql",
 			'--section' => 'pre-data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -699,6 +753,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/section_data.sql",
 			'--section' => 'data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -707,6 +762,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			'--file' => "$tempdir/section_post_data.sql",
 			'--section' => 'post-data',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -717,6 +773,7 @@ my %pgdump_runs = (
 			'--schema' => 'dump_test',
 			'--large-objects',
 			'--no-large-objects',
+			'--with-statistics',
 			'postgres',
 		],
 	},
@@ -732,6 +789,7 @@ my %pgdump_runs = (
 			'pg_dump', '--no-sync',
 			"--file=$tempdir/no_data_no_schema.sql", '--no-data',
 			'--no-schema', 'postgres',
+			'--with-statistics',
 		],
 	},
 	statistics_only => {
@@ -752,6 +810,7 @@ my %pgdump_runs = (
 		dump_cmd => [
 			'pg_dump', '--no-sync',
 			"--file=$tempdir/no_schema.sql", '--no-schema',
+			'--with-statistics',
 			'postgres',
 		],
 	},);
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 23cb08e8347..183f08ce1e8 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -58,7 +58,7 @@ generate_old_dump(void)
 						   (user_opts.transfer_mode == TRANSFER_MODE_SWAP) ?
 						   "" : "--sequence-data",
 						   log_opts.verbose ? "--verbose" : "",
-						   user_opts.do_statistics ? "" : "--no-statistics",
+						   user_opts.do_statistics ? "--with-statistics" : "--no-statistics",
 						   log_opts.dumpdir,
 						   sql_file_name, escaped_connstr.data);
 
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 87+ messages in thread


end of thread, other threads:[~2025-05-22 21:29 UTC | newest]

Thread overview: 87+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-08-04 00:02 [PATCH] Add new GUC compression_algorithm Petr Jelinek <[email protected]>
2020-12-03 15:19 [PATCH 3/3] Extended statistics on expressions Tomas Vondra <[email protected]>
2024-01-22 06:09 Re: Statistics Import and Export Peter Smith <[email protected]>
2025-03-07 00:58 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-07 01:47   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-07 01:42 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-07 02:56   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-07 16:22   ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-03-07 16:53     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-07 17:41   ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-03-07 18:41     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-07 20:46       ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-03-07 21:43         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-08 03:43           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-08 05:51             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-03-08 07:51               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-08 07:56                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-08 03:40         ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-08 15:56           ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-03-08 19:09             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-25 05:32               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-25 17:51                 ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-03-09 17:00             ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-14 20:03               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-16 01:37                 ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-19 22:17                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-19 22:35                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-25 06:53                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-25 14:53                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-25 18:42                         ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-25 19:59                           ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-26 01:41                             ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-29 01:11                               ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-29 05:29                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-03-29 05:44                                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-31 15:11                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-04-01 02:33                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-01 03:02                                         ` Re: Statistics Import and Export Robert Treat <[email protected]>
2025-04-01 18:20                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-01 18:44                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-02 03:21                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-02 05:44                                               ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-04-02 16:42                                                 ` Re: Statistics Import and Export Andres Freund <[email protected]>
2025-04-03 02:26                                                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-03 22:23                                                   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-04-04 02:19                                                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-04 19:56                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-04 20:06                                                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-04 20:58                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-04 22:25                                                             ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-04-05 02:06                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-14 00:01                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-05-14 15:53                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-14 20:30                                                                     ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-05-16 18:47                                                                       ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-05-19 01:52                                                                         ` Re: Statistics Import and Export Michael Paquier <[email protected]>
2025-05-19 16:51                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-19 21:13                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-05-19 21:31                                                                           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-20 17:32                                                                             ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-05-21 16:08                                                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-21 19:33                                                                                 ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-05-21 21:14                                                                                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-05-21 21:29                                                                                   ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-21 23:11                                                                                     ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-05-21 23:53                                                                                     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-05-22 15:25                                                                                       ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-22 19:25                                                                                         ` Re: Statistics Import and Export Hari Krishna Sunder <[email protected]>
2025-04-03 02:38                                               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-16 20:33                 ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-16 21:32                   ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-17 14:23                     ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-17 23:24                       ` Re: Statistics Import and Export Corey Huinker <[email protected]>
2025-03-18 01:01                         ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-03-31 17:39 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-03-31 22:04   ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-04-01 20:24     ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-05-10 19:51       ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
2025-05-22 14:20         ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-05-22 14:30           ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-22 14:53             ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-05-22 18:52           ` Re: Statistics Import and Export Jeff Davis <[email protected]>
2025-05-22 19:29             ` Re: Statistics Import and Export Greg Sabino Mullane <[email protected]>
2025-05-22 19:36               ` Re: Statistics Import and Export Nathan Bossart <[email protected]>
2025-05-22 19:36                 ` Re: Statistics Import and Export Robert Haas <[email protected]>
2025-05-22 19:41               ` Re: Statistics Import and Export Tom Lane <[email protected]>
2025-05-22 21:29                 ` Re: Statistics Import and Export Jeff Davis <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox