vViaDellaAttnums-0002-Dump-expression-index-stats-by-attn.patch

text/x-patch

Filename: vViaDellaAttnums-0002-Dump-expression-index-stats-by-attn.patch
Type: text/x-patch
Part: 1
Message: Re: Statistics Import and Export

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch 0002
Subject: Dump expression index stats by attnum, not attname.
File+
src/bin/pg_dump/pg_dump.c 123 96
src/bin/pg_dump/pg_dump.h 2 0
From 92f75fc9835e7982216d32329caa34b26347a6b3 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Wed, 26 Feb 2025 04:08:31 -0500
Subject: [PATCH vViaDellaAttnums 2/2] Dump expression index stats by attnum,
 not attname.

Names of columns in index expressions are not stable across major
versions, so we are forced to dump those by attnum instead.
---
 src/bin/pg_dump/pg_dump.c | 219 +++++++++++++++++++++-----------------
 src/bin/pg_dump/pg_dump.h |   2 +
 2 files changed, 125 insertions(+), 96 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 0de6c959bb0..65413e07899 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6819,7 +6819,8 @@ getFuncs(Archive *fout)
  */
 static RelStatsInfo *
 getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
-					  float reltuples, int32 relallvisible, char relkind)
+					  float reltuples, int32 relallvisible, char relkind,
+					  char **indexprattnames, int nindexprattnames)
 {
 	if (!fout->dopt->dumpStatistics)
 		return NULL;
@@ -6844,6 +6845,8 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
 		dobj->components |= DUMP_COMPONENT_STATISTICS;
 		dobj->name = pg_strdup(rel->name);
 		dobj->namespace = rel->namespace;
+		info->indexprattnames = indexprattnames;
+		info->nindexprattnames = nindexprattnames;
 		info->relpages = relpages;
 		info->reltuples = reltuples;
 		info->relallvisible = relallvisible;
@@ -7249,7 +7252,8 @@ getTables(Archive *fout, int *numTables)
 		/* Add statistics */
 		if (tblinfo[i].interesting)
 			getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages,
-								  reltuples, relallvisible, tblinfo[i].relkind);
+								  reltuples, relallvisible, tblinfo[i].relkind,
+								  NULL, 0);
 
 		/*
 		 * Read-lock target tables to make sure they aren't DROPPED or altered
@@ -7537,7 +7541,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_indexprattnames;
 
 	/*
 	 * We want to perform just one query against pg_index.  However, we
@@ -7579,6 +7584,10 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 						 "c.tableoid AS contableoid, "
 						 "c.oid AS conoid, "
 						 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
+						 "(SELECT pg_catalog.array_agg(attname ORDER BY attnum) "
+						 "  FROM pg_catalog.pg_attribute "
+						 "  WHERE attrelid = i.indexrelid AND "
+						 "    i.indexprs IS NOT NULL) AS indexprattnames, "
 						 "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 						 "t.reloptions AS indreloptions, ");
 
@@ -7702,6 +7711,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	i_indreloptions = PQfnumber(res, "indreloptions");
 	i_indstatcols = PQfnumber(res, "indstatcols");
 	i_indstatvals = PQfnumber(res, "indstatvals");
+	i_indexprattnames = PQfnumber(res, "indexprattnames");
 
 	indxinfo = (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
 
@@ -7715,6 +7725,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		Oid			indrelid = atooid(PQgetvalue(res, j, i_indrelid));
 		TableInfo  *tbinfo = NULL;
 		int			numinds;
+		char	  **indexprattnames = NULL;	/* attnames for expression indexes only */
+		int			nindexprattnames = 0;	/* number of attnames for expression indexes only */
 
 		/* Count rows for this table */
 		for (numinds = 1; numinds < ntups - j; numinds++)
@@ -7784,9 +7796,16 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			else
 				indexkind = RELKIND_PARTITIONED_INDEX;
 
-			contype = *(PQgetvalue(res, j, i_contype));
+			if (!PQgetisnull(res, j, i_indexprattnames))
+				if (!parsePGArray(PQgetvalue(res, j, i_indexprattnames),
+								   &indexprattnames, &nindexprattnames))
+				pg_fatal("could not parse %s array", "indattnames");
+
 			relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages,
-											 reltuples, relallvisible, indexkind);
+											 reltuples, relallvisible, indexkind,
+											 indexprattnames, nindexprattnames);
+
+			contype = *(PQgetvalue(res, j, i_contype));
 
 			if (contype == 'p' || contype == 'u' || contype == 'x')
 			{
@@ -10410,28 +10429,6 @@ dumpComment(Archive *fout, const char *type,
 						catalogId, subid, dumpId, NULL);
 }
 
-/*
- * Tabular description of the parameters to pg_restore_attribute_stats()
- * param_name, param_type
- */
-static const char *att_stats_arginfo[][2] = {
-	{"attname", "name"},
-	{"inherited", "boolean"},
-	{"null_frac", "float4"},
-	{"avg_width", "integer"},
-	{"n_distinct", "float4"},
-	{"most_common_vals", "text"},
-	{"most_common_freqs", "float4[]"},
-	{"histogram_bounds", "text"},
-	{"correlation", "float4"},
-	{"most_common_elems", "text"},
-	{"most_common_elem_freqs", "float4[]"},
-	{"elem_count_histogram", "float4[]"},
-	{"range_length_histogram", "text"},
-	{"range_empty_frac", "float4"},
-	{"range_bounds_histogram", "text"},
-};
-
 /*
  * appendNamedArgument --
  *
@@ -10440,9 +10437,9 @@ static const char *att_stats_arginfo[][2] = {
  */
 static void
 appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
-					const char *argval, const char *argtype)
+					const char *argtype, const char *argval)
 {
-	appendPQExpBufferStr(out, "\t");
+	appendPQExpBufferStr(out, ",\n\t");
 
 	appendStringLiteralAH(out, argname, fout);
 	appendPQExpBufferStr(out, ", ");
@@ -10451,68 +10448,6 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
 	appendPQExpBuffer(out, "::%s", argtype);
 }
 
-/*
- * appendRelStatsImport --
- *
- * Append a formatted pg_restore_relation_stats statement.
- */
-static void
-appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo,
-					 const char *qualified_name)
-{
-	char		reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
-
-	float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
-
-	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
-	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
-					  fout->remoteVersion);
-	appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", qualified_name);
-	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
-	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
-	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
-					  rsinfo->relallvisible);
-}
-
-/*
- * appendAttStatsImport --
- *
- * Append a series of formatted pg_restore_attribute_stats statements.
- */
-static void
-appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res,
-					 const char *qualified_name)
-{
-	for (int rownum = 0; rownum < PQntuples(res); rownum++)
-	{
-		const char *sep = "";
-
-		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
-		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
-						  fout->remoteVersion);
-		appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n",
-						  qualified_name);
-		for (int argno = 0; argno < lengthof(att_stats_arginfo); argno++)
-		{
-			const char *argname = att_stats_arginfo[argno][0];
-			const char *argtype = att_stats_arginfo[argno][1];
-			int			fieldno = PQfnumber(res, argname);
-
-			if (fieldno < 0)
-				pg_fatal("attribute stats export query missing field '%s'",
-						 argname);
-
-			if (PQgetisnull(res, rownum, fieldno))
-				continue;
-
-			appendPQExpBufferStr(out, sep);
-			appendNamedArgument(out, fout, argname, PQgetvalue(res, rownum, fieldno), argtype);
-			sep = ",\n";
-		}
-		appendPQExpBufferStr(out, "\n);\n");
-	}
-}
-
 /*
  * Decide which section to use based on the relkind of the parent object.
  *
@@ -10557,6 +10492,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 	DumpId	   *deps = NULL;
 	int			ndeps = 0;
 	const char *qualified_name;
+	char		reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN];
 
 	/* nothing to do if we are not dumping statistics */
 	if (!fout->dopt->dumpStatistics)
@@ -10606,6 +10542,22 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 		resetPQExpBuffer(query);
 	}
 
+	out = createPQExpBuffer();
+
+	qualified_name = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name,
+									rsinfo->dobj.name);
+
+	/* restore relation stats */
+	float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str);
+	appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n");
+	appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+					  fout->remoteVersion);
+	appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", qualified_name);
+	appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages);
+	appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str);
+	appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n",
+					  rsinfo->relallvisible);
+
 	appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
 	appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
 	appendPQExpBufferStr(query, ", ");
@@ -10614,13 +10566,88 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
-	out = createPQExpBuffer();
+	/* restore attribute stats */
+	for (int rownum = 0; rownum < PQntuples(res); rownum++)
+	{
+		const char *attname;
 
-	qualified_name = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name,
-									rsinfo->dobj.name);
+		appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
+		appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
+						  fout->remoteVersion);
+		appendPQExpBuffer(out, "\t'relation', '%s'::regclass",
+						  qualified_name);
 
-	appendRelStatsImport(out, fout, rsinfo, qualified_name);
-	appendAttStatsImport(out, fout, res, qualified_name);
+
+		if (PQgetisnull(res, rownum, 0))
+			pg_fatal("attname cannot be NULL");
+		attname = PQgetvalue(res, rownum, 0);
+
+		/*
+		 * Expression indexes look up attname in attnames to derive attnum,
+		 * all others use attname directly.
+		 */
+		if (rsinfo->nindexprattnames == 0)
+			appendNamedArgument(out, fout, "attname", "name", attname);
+		else
+		{
+			bool found = false;
+
+			for (int i = 0; i < rsinfo->nindexprattnames; i++)
+				if (strcmp(attname, rsinfo->indexprattnames[i]) == 0)
+				{
+					appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint", (AttrNumber) i + 1);
+					found = true;
+					break;
+				}
+
+			if (!found)
+				pg_fatal("unable to find attname '%s'", attname);
+		}
+
+		if (!PQgetisnull(res, rownum, 1))
+			appendNamedArgument(out, fout, "inherited", "boolean",
+								PQgetvalue(res, rownum, 1));
+		if (!PQgetisnull(res, rownum, 2))
+			appendNamedArgument(out, fout, "null_frac", "float4",
+								PQgetvalue(res, rownum, 2));
+		if (!PQgetisnull(res, rownum, 3))
+			appendNamedArgument(out, fout, "avg_width", "integer",
+								PQgetvalue(res, rownum, 3));
+		if (!PQgetisnull(res, rownum, 4))
+			appendNamedArgument(out, fout, "n_distinct", "float4",
+								PQgetvalue(res, rownum, 4));
+		if (!PQgetisnull(res, rownum, 5))
+			appendNamedArgument(out, fout, "most_common_vals", "text",
+								PQgetvalue(res, rownum, 5));
+		if (!PQgetisnull(res, rownum, 6))
+			appendNamedArgument(out, fout, "most_common_freqs", "float4[]",
+								PQgetvalue(res, rownum, 6));
+		if (!PQgetisnull(res, rownum, 7))
+			appendNamedArgument(out, fout, "histogram_bounds", "text",
+								PQgetvalue(res, rownum, 7));
+		if (!PQgetisnull(res, rownum, 8))
+			appendNamedArgument(out, fout, "correlation", "float4",
+								PQgetvalue(res, rownum, 8));
+		if (!PQgetisnull(res, rownum, 9))
+			appendNamedArgument(out, fout, "most_common_elems", "text",
+								PQgetvalue(res, rownum, 9));
+		if (!PQgetisnull(res, rownum, 10))
+			appendNamedArgument(out, fout, "most_common_elem_freqs", "float4[]",
+								PQgetvalue(res, rownum, 10));
+		if (!PQgetisnull(res, rownum, 11))
+			appendNamedArgument(out, fout, "elem_count_histogram", "float4[]",
+								PQgetvalue(res, rownum, 11));
+		if (!PQgetisnull(res, rownum, 12))
+			appendNamedArgument(out, fout, "range_length_histogram", "text",
+								PQgetvalue(res, rownum, 12));
+		if (!PQgetisnull(res, rownum, 13))
+			appendNamedArgument(out, fout, "range_empty_frac", "float4",
+								PQgetvalue(res, rownum, 13));
+		if (!PQgetisnull(res, rownum, 14))
+			appendNamedArgument(out, fout, "range_bounds_histogram", "text",
+								PQgetvalue(res, rownum, 14));
+		appendPQExpBufferStr(out, "\n);\n");
+	}
 
 	PQclear(res);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 9d6a4857c4b..0584b1c7abb 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -438,6 +438,8 @@ typedef struct _indexAttachInfo
 typedef struct _relStatsInfo
 {
 	DumpableObject dobj;
+	char	  **indexprattnames;	/* attnames in an expression index */
+	int32		nindexprattnames;	/* number of attnames for expression indexes, else 0 */
 	int32		relpages;
 	float		reltuples;
 	int32		relallvisible;
-- 
2.48.1