v29-0002-Create-functions-pg_set_relation_stats-pg_clear_.patch
text/x-patch
Filename: v29-0002-Create-functions-pg_set_relation_stats-pg_clear_.patch
Type: text/x-patch
Part: 3
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 v29-0002
Subject: Create functions pg_set_relation_stats, pg_clear_relation_stats.
| File | + | − |
|---|---|---|
| doc/src/sgml/func.sgml | 101 | 0 |
| src/backend/statistics/Makefile | 1 | 0 |
| src/backend/statistics/meson.build | 1 | 0 |
| src/backend/statistics/relation_stats.c | 211 | 0 |
| src/include/catalog/pg_proc.dat | 15 | 0 |
| src/test/regress/expected/stats_import.out | 128 | 0 |
| src/test/regress/parallel_schedule | 1 | 1 |
| src/test/regress/sql/stats_import.sql | 88 | 0 |
From 3c4115f665e80e67e4bf56439e95bd3577c0ca42 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Mon, 16 Sep 2024 05:02:32 -0400
Subject: [PATCH v29 2/7] Create functions pg_set_relation_stats,
pg_clear_relation_stats.
These functions are used to tweak statistics on any relation, provided
that the user has MAINTAIN privilege on the relation, or is the database
owner.
These functions set the following attributes in pg_class: relpages,
reltuples, and relallvisible. Future versions may set any new stats-related
attributes.
---
src/include/catalog/pg_proc.dat | 15 ++
src/backend/statistics/Makefile | 1 +
src/backend/statistics/meson.build | 1 +
src/backend/statistics/relation_stats.c | 211 +++++++++++++++++++++
src/test/regress/expected/stats_import.out | 128 +++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/stats_import.sql | 88 +++++++++
doc/src/sgml/func.sgml | 101 ++++++++++
8 files changed, 546 insertions(+), 1 deletion(-)
create mode 100644 src/backend/statistics/relation_stats.c
create mode 100644 src/test/regress/expected/stats_import.out
create mode 100644 src/test/regress/sql/stats_import.sql
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 53a081ed88..24279c3078 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12309,5 +12309,20 @@
proallargtypes => '{int8,pg_lsn,pg_lsn,int4}', proargmodes => '{o,o,o,o}',
proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}',
prosrc => 'pg_get_wal_summarizer_state' },
+ +# Statistics Import
+{ oid => '8048',
+ descr => 'set statistics on relation',
+ proname => 'pg_set_relation_stats', provolatile => 'v', proisstrict => 'f',
+ proparallel => 'u', prorettype => 'bool',
+ proargtypes => 'regclass int4 float4 int4',
+ proargnames => '{relation,relpages,reltuples,relallvisible}',
+ prosrc => 'pg_set_relation_stats' },
+{ oid => '8049',
+ descr => 'clear statistics on relation',
+ proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
+ proparallel => 'u', prorettype => 'bool',
+ proargtypes => 'regclass',
+ proargnames => '{relation}',
+ prosrc => 'pg_clear_relation_stats' },
]
diff --git a/src/backend/statistics/Makefile b/src/backend/statistics/Makefile
index 3ffc8f38e6..f38c874986 100644
--- a/src/backend/statistics/Makefile
+++ b/src/backend/statistics/Makefile
@@ -17,6 +17,7 @@ OBJS = \
extended_stats.o \
mcv.o \
mvdistinct.o \
+ relation_stats.o \
stats_utils.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/statistics/meson.build b/src/backend/statistics/meson.build
index 74c5dc6afa..376bd2bee1 100644
--- a/src/backend/statistics/meson.build
+++ b/src/backend/statistics/meson.build
@@ -5,5 +5,6 @@ backend_sources += files(
'extended_stats.c',
'mcv.c',
'mvdistinct.c',
+ 'relation_stats.c',
'stats_utils.c'
)
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
new file mode 100644
index 0000000000..a6143cc674
--- /dev/null
+++ b/src/backend/statistics/relation_stats.c
@@ -0,0 +1,211 @@
+/*-------------------------------------------------------------------------
+ * relation_stats.c
+ *
+ * PostgreSQL relation statistics manipulation
+ *
+ * Code supporting the direct import of relation statistics, similar to
+ * what is done by the ANALYZE command.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/statistics/relation_stats.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_type_d.h"
+#include "statistics/stats_utils.h"
+#include "utils/fmgrprotos.h"
+#include "utils/syscache.h"
+
+#define DEFAULT_RELPAGES Int32GetDatum(0)
+#define DEFAULT_RELTUPLES Float4GetDatum(-1.0)
+#define DEFAULT_RELALLVISIBLE Int32GetDatum(0)
+
+/*
+ * Positional argument numbers, names, and types for
+ * relation_statistics_update().
+ */
+
+typedef enum
+{
+ RELATION_ARG = 0,
+ RELPAGES_ARG,
+ RELTUPLES_ARG,
+ RELALLVISIBLE_ARG,
+ NUM_RELATION_STATS_ARGS
+} relation_stats_argnum;
+
+static StatsArgInfo relarginfo[] =
+{
+ [RELATION_ARG] = {"relation", REGCLASSOID},
+ [RELPAGES_ARG] = {"relpages", INT4OID},
+ [RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
+ [RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
+ [NUM_RELATION_STATS_ARGS] = {0}
+};
+
+static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel);
+
+/*
+ * Internal function for modifying statistics for a relation.
+ */
+static bool
+relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
+{
+ Oid reloid;
+ Relation crel;
+ HeapTuple ctup;
+ Form_pg_class pgcform;
+ int replaces[3] = {0};
+ Datum values[3] = {0};
+ bool nulls[3] = {false, false, false};
+ int ncols = 0;
+ TupleDesc tupdesc;
+ HeapTuple newtup;
+
+
+ stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
+ reloid = PG_GETARG_OID(RELATION_ARG);
+
+ stats_lock_check_privileges(reloid);
+
+ /*
+ * Take RowExclusiveLock on pg_class, consistent with
+ * vac_update_relstats().
+ */
+ crel = table_open(RelationRelationId, RowExclusiveLock);
+
+ tupdesc = RelationGetDescr(crel);
+ ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
+ if (!HeapTupleIsValid(ctup))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("pg_class entry for relid %u not found", reloid)));
+ table_close(crel, RowExclusiveLock);
+ return false;
+ }
+
+ pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+ /* relpages */
+ if (!PG_ARGISNULL(RELPAGES_ARG))
+ {
+ int32 relpages = PG_GETARG_INT32(RELPAGES_ARG);
+
+ if (relpages < -1)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relpages cannot be < -1")));
+ table_close(crel, RowExclusiveLock);
+ return false;
+ }
+
+ if (relpages != pgcform->relpages)
+ {
+ replaces[ncols] = Anum_pg_class_relpages;
+ values[ncols] = Int32GetDatum(relpages);
+ ncols++;
+ }
+ }
+
+ if (!PG_ARGISNULL(RELTUPLES_ARG))
+ {
+ float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
+
+ if (reltuples < -1.0)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("reltuples cannot be < -1.0")));
+ table_close(crel, RowExclusiveLock);
+ return false;
+ }
+
+ if (reltuples != pgcform->reltuples)
+ {
+ replaces[ncols] = Anum_pg_class_reltuples;
+ values[ncols] = Float4GetDatum(reltuples);
+ ncols++;
+ }
+ }
+
+ if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
+ {
+ int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
+
+ if (relallvisible < 0)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relallvisible cannot be < 0")));
+ table_close(crel, RowExclusiveLock);
+ return false;
+ }
+
+ if (relallvisible != pgcform->relallvisible)
+ {
+ replaces[ncols] = Anum_pg_class_relallvisible;
+ values[ncols] = Int32GetDatum(relallvisible);
+ ncols++;
+ }
+ }
+
+ /* only update pg_class if there is a meaningful change */
+ if (ncols == 0)
+ {
+ table_close(crel, RowExclusiveLock);
+ return false;
+ }
+
+ newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values,
+ nulls);
+
+ CatalogTupleUpdate(crel, &newtup->t_self, newtup);
+ heap_freetuple(newtup);
+
+ /* release the lock, consistent with vac_update_relstats() */
+ table_close(crel, RowExclusiveLock);
+
+ return true;
+}
+
+/*
+ * Set statistics for a given pg_class entry.
+ */
+Datum
+pg_set_relation_stats(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(relation_statistics_update(fcinfo, ERROR));
+}
+
+/*
+ * Clear statistics for a given pg_class entry; that is, set back to initial
+ * stats for a newly-created table.
+ */
+Datum
+pg_clear_relation_stats(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(newfcinfo, 4);
+
+ InitFunctionCallInfoData(*newfcinfo, NULL, 4, InvalidOid, NULL, NULL);
+
+ newfcinfo->args[0].value = PG_GETARG_OID(0);
+ newfcinfo->args[0].isnull = PG_ARGISNULL(0);
+ newfcinfo->args[1].value = DEFAULT_RELPAGES;
+ newfcinfo->args[1].isnull = false;
+ newfcinfo->args[2].value = DEFAULT_RELTUPLES;
+ newfcinfo->args[2].isnull = false;
+ newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE;
+ newfcinfo->args[3].isnull = false;
+
+ PG_RETURN_BOOL(relation_statistics_update(newfcinfo, ERROR));
+}
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
new file mode 100644
index 0000000000..951dd6a10e
--- /dev/null
+++ b/src/test/regress/expected/stats_import.out
@@ -0,0 +1,128 @@
+CREATE SCHEMA stats_import;
+CREATE TYPE stats_import.complex_type AS (
+ a integer,
+ b real,
+ c text,
+ d date,
+ e jsonb);
+CREATE TABLE stats_import.test(
+ id INTEGER PRIMARY KEY,
+ name text,
+ comp stats_import.complex_type,
+ arange int4range,
+ tags text[]
+);
+-- starting stats
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
+-- error: regclass not found
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 0::Oid,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+ERROR: could not open relation with OID 0
+-- relpages default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => NULL::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+ pg_set_relation_stats
+-----------------------
+ t
+(1 row)
+
+-- reltuples default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => NULL::real,
+ relallvisible => 4::integer);
+ pg_set_relation_stats
+-----------------------
+ t
+(1 row)
+
+-- relallvisible default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => NULL::integer);
+ pg_set_relation_stats
+-----------------------
+ f
+(1 row)
+
+-- named arguments
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+ pg_set_relation_stats
+-----------------------
+ f
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 17 | 400 | 4
+(1 row)
+
+-- positional arguments
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ 'stats_import.test'::regclass,
+ 18::integer,
+ 401.0::real,
+ 5::integer);
+ pg_set_relation_stats
+-----------------------
+ t
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 18 | 401 | 5
+(1 row)
+
+-- clear
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.test'::regclass);
+ pg_clear_relation_stats
+-------------------------
+ t
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
+DROP SCHEMA stats_import CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to type stats_import.complex_type
+drop cascades to table stats_import.test
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7a5a910562..0d0428518f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -28,7 +28,7 @@ test: strings md5 numerology point lseg line box path polygon circle date time t
# geometry depends on point, lseg, line, box, path, polygon, circle
# horology depends on date, time, timetz, timestamp, timestamptz, interval
# ----------
-test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import
# ----------
# Load huge amounts of data
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
new file mode 100644
index 0000000000..40199dd453
--- /dev/null
+++ b/src/test/regress/sql/stats_import.sql
@@ -0,0 +1,88 @@
+CREATE SCHEMA stats_import;
+
+CREATE TYPE stats_import.complex_type AS (
+ a integer,
+ b real,
+ c text,
+ d date,
+ e jsonb);
+
+CREATE TABLE stats_import.test(
+ id INTEGER PRIMARY KEY,
+ name text,
+ comp stats_import.complex_type,
+ arange int4range,
+ tags text[]
+);
+
+-- starting stats
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- error: regclass not found
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 0::Oid,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+
+-- relpages default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => NULL::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+
+-- reltuples default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => NULL::real,
+ relallvisible => 4::integer);
+
+-- relallvisible default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => NULL::integer);
+
+-- named arguments
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- positional arguments
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ 'stats_import.test'::regclass,
+ 18::integer,
+ 401.0::real,
+ 5::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- clear
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.test'::regclass);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+DROP SCHEMA stats_import CASCADE;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 84eb3a45ee..8a715934fe 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30129,6 +30129,107 @@ DETAIL: Make sure pg_wal_replay_wait() isn't called within a transaction with a
</tgroup>
</table>
+ <table id="functions-admin-statsmods">
+ <title>Database Object Statistics Modification Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry">
+ <para role="func_signature">
+ <indexterm>
+ <primary>pg_set_relation_stats</primary>
+ </indexterm>
+ <function>pg_set_relation_stats</function> (
+ <parameter>relation</parameter> <type>regclass</type>
+ <optional>, <parameter>relpages</parameter> <type>integer</type></optional>
+ <optional>, <parameter>reltuples</parameter> <type>real</type></optional>
+ <optional>, <parameter>relallvisible</parameter> <type>integer</type></optional> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Updates relation-level statistics for the given relation to the
+ specified values. The parameters correspond to columns in <link
+ linkend="catalog-pg-class"><structname>pg_class</structname></link>. Unspecified
+ or <literal>NULL</literal> values leave the setting
+ unchanged. Returns <literal>true</literal> if a change was made;
+ <literal>false</literal> otherwise.
+ </para>
+ <para>
+ Ordinarily, these statistics are collected automatically or updated
+ as a part of <xref linkend="sql-vacuum"/> or <xref
+ linkend="sql-analyze"/>, so it's not necessary to call this
+ function. However, it may be useful when testing the effects of
+ statistics on the planner to understand or anticipate plan changes.
+ </para>
+ <para>
+ The caller must have the <literal>MAINTAIN</literal> privilege on
+ the table or be the owner of the database.
+ </para>
+ <para>
+ The value of <structfield>relpages</structfield> must be greater than
+ or equal to <literal>0</literal>,
+ <structfield>reltuples</structfield> must be greater than or equal to
+ <literal>-1.0</literal>, and <structfield>relallvisible</structfield>
+ must be greater than or equal to <literal>0</literal>.
+ </para>
+ <warning>
+ <para>
+ Changes made by this function are likely to be overwritten by <link
+ linkend="autovacuum">autovacuum</link> (or manual
+ <command>VACUUM</command> or <command>ANALYZE</command>) and should
+ be considered temporary.
+ </para>
+ <para>
+ The signature of this function may change in new major releases if
+ there are changes to the statistics being tracked.
+ </para>
+ </warning>
+ </entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry">
+ <para role="func_signature">
+ <indexterm>
+ <primary>pg_clear_relation_stats</primary>
+ </indexterm>
+ <function>pg_clear_relation_stats</function> ( <parameter>relation</parameter> <type>regclass</type> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Clears table-level statistics for the given relation, as though the
+ table was newly created. Returns <literal>true</literal> if a change
+ was made; <literal>false</literal> otherwise.
+ </para>
+ <para>
+ The caller must have the <literal>MAINTAIN</literal> privilege on
+ the table or be the owner of the database.
+ </para>
+ <warning>
+ <para>
+ Changes made by this function are likely to be overwritten by <link
+ linkend="autovacuum">autovacuum</link> (or manual
+ <command>VACUUM</command> or <command>ANALYZE</command>) and should
+ be considered temporary.
+ </para>
+ </warning>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
<para>
<xref linkend="functions-info-partition"/> lists functions that provide
information about the structure of partitioned tables.
--
2.46.0