</tgroup>
</table>
+ <para>
+ <xref linkend="functions-admin-statsmod"/> lists functions used to
+ manipulate statistics.
+ <warning>
+ <para>
+ Changes made by these statistics manipulation functions 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>
+ </para>
+
+ <table id="functions-admin-statsmod">
+ <title>Database Object Statistics Manipulation 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>
+ </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>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
<para>
<xref linkend="functions-info-partition"/> lists functions that provide
information about the structure of partitioned tables.
CALLED ON NULL INPUT VOLATILE PARALLEL SAFE
AS 'pg_stat_reset_slru';
+CREATE OR REPLACE FUNCTION
+ pg_set_relation_stats(relation regclass,
+ relpages integer DEFAULT NULL,
+ reltuples real DEFAULT NULL,
+ relallvisible integer DEFAULT NULL)
+RETURNS bool
+LANGUAGE INTERNAL
+CALLED ON NULL INPUT VOLATILE
+AS 'pg_set_relation_stats';
+
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
dependencies.o \
extended_stats.o \
mcv.o \
- mvdistinct.o
+ mvdistinct.o \
+ relation_stats.o \
+ stat_utils.o
include $(top_srcdir)/src/backend/common.mk
'extended_stats.c',
'mcv.c',
'mvdistinct.c',
+ 'relation_stats.c',
+ 'stat_utils.c'
)
--- /dev/null
+/*-------------------------------------------------------------------------
+ * 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 "statistics/stat_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().
+ */
+
+enum relation_stats_argnum
+{
+ RELATION_ARG = 0,
+ RELPAGES_ARG,
+ RELTUPLES_ARG,
+ RELALLVISIBLE_ARG,
+ NUM_RELATION_STATS_ARGS
+};
+
+static struct 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] = {0};
+ 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));
+}
--- /dev/null
+/*-------------------------------------------------------------------------
+ * stat_utils.c
+ *
+ * PostgreSQL statistics manipulation utilities.
+ *
+ * Code supporting the direct manipulation of statistics.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/statistics/stat_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/relation.h"
+#include "catalog/pg_database.h"
+#include "miscadmin.h"
+#include "statistics/stat_utils.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/acl.h"
+#include "utils/rel.h"
+
+/*
+ * Ensure that a given argument is not null.
+ */
+void
+stats_check_required_arg(FunctionCallInfo fcinfo,
+ struct StatsArgInfo *arginfo,
+ int argnum)
+{
+ if (PG_ARGISNULL(argnum))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" cannot be NULL",
+ arginfo[argnum].argname)));
+}
+
+/*
+ * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
+ * relation (but retain the lock).
+ *
+ * A role has privileges to set statistics on the relation if any of the
+ * following are true:
+ * - the role owns the current database and the relation is not shared
+ * - the role has the MAINTAIN privilege on the relation
+ */
+void
+stats_lock_check_privileges(Oid reloid)
+{
+ Relation rel = relation_open(reloid, ShareUpdateExclusiveLock);
+ const char relkind = rel->rd_rel->relkind;
+
+ /* All of the types that can be used with ANALYZE, plus indexes */
+ switch (relkind)
+ {
+ case RELKIND_RELATION:
+ case RELKIND_INDEX:
+ case RELKIND_MATVIEW:
+ case RELKIND_FOREIGN_TABLE:
+ case RELKIND_PARTITIONED_TABLE:
+ case RELKIND_PARTITIONED_INDEX:
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot modify statistics for relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+ }
+
+ if (rel->rd_rel->relisshared)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot modify statistics for shared relation")));
+
+ if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+ {
+ AclResult aclresult = pg_class_aclcheck(RelationGetRelid(rel),
+ GetUserId(),
+ ACL_MAINTAIN);
+
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult,
+ get_relkind_objtype(rel->rd_rel->relkind),
+ NameStr(rel->rd_rel->relname));
+ }
+
+ relation_close(rel, NoLock);
+}
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202410113
+#define CATALOG_VERSION_NO 202410114
#endif
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 => '9944',
+ 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 => '9945',
+ 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' },
# GiST stratnum implementations
{ oid => '8047', descr => 'GiST support',
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * stat_utils.h
+ * Extended statistics and selectivity estimation functions.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/statistics/stat_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STATS_UTILS_H
+#define STATS_UTILS_H
+
+#include "fmgr.h"
+
+struct StatsArgInfo
+{
+ const char *argname;
+ Oid argtype;
+};
+
+extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+ struct StatsArgInfo *arginfo,
+ int argnum);
+extern void stats_lock_check_privileges(Oid reloid);
+
+#endif /* STATS_UTILS_H */
--- /dev/null
+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)
+
+-- invalid relkinds for statistics
+CREATE SEQUENCE stats_import.testseq;
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.testseq'::regclass);
+ERROR: cannot modify statistics for relation "testseq"
+DETAIL: This operation is not supported for sequences.
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.testview'::regclass);
+ERROR: cannot modify statistics for relation "testview"
+DETAIL: This operation is not supported for views.
+DROP SCHEMA stats_import CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to type stats_import.complex_type
+drop cascades to table stats_import.test
+drop cascades to sequence stats_import.testseq
+drop cascades to view stats_import.testview
# 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
--- /dev/null
+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;
+
+-- invalid relkinds for statistics
+CREATE SEQUENCE stats_import.testseq;
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.testseq'::regclass);
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.testview'::regclass);
+
+DROP SCHEMA stats_import CASCADE;