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.

Bump catalog version.

Author: Corey Huinker
Discussion: https://postgr.es/m/CADkLM=eErgzn7ECDpwFcptJKOk9SxZEk5Pot4d94eVTZsvj3gw@mail.gmail.com
pull/182/head
Jeff Davis 9 months ago
parent 6f782a2a17
commit e839c8ecc9
  1. 94
      doc/src/sgml/func.sgml
  2. 10
      src/backend/catalog/system_functions.sql
  3. 4
      src/backend/statistics/Makefile
  4. 2
      src/backend/statistics/meson.build
  5. 210
      src/backend/statistics/relation_stats.c
  6. 94
      src/backend/statistics/stat_utils.c
  7. 2
      src/include/catalog/catversion.h
  8. 15
      src/include/catalog/pg_proc.dat
  9. 29
      src/include/statistics/stat_utils.h
  10. 143
      src/test/regress/expected/stats_import.out
  11. 2
      src/test/regress/parallel_schedule
  12. 98
      src/test/regress/sql/stats_import.sql

@ -30135,6 +30135,100 @@ DETAIL: Make sure pg_wal_replay_wait() isn't called within a transaction with a
</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.

@ -639,6 +639,16 @@ LANGUAGE INTERNAL
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

@ -16,6 +16,8 @@ OBJS = \
dependencies.o \
extended_stats.o \
mcv.o \
mvdistinct.o
mvdistinct.o \
relation_stats.o \
stat_utils.o
include $(top_srcdir)/src/backend/common.mk

@ -5,4 +5,6 @@ backend_sources += files(
'extended_stats.c',
'mcv.c',
'mvdistinct.c',
'relation_stats.c',
'stat_utils.c'
)

@ -0,0 +1,210 @@
/*-------------------------------------------------------------------------
* 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));
}

@ -0,0 +1,94 @@
/*-------------------------------------------------------------------------
* 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);
}

@ -57,6 +57,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202410113
#define CATALOG_VERSION_NO 202410114
#endif

@ -12341,6 +12341,21 @@
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',

@ -0,0 +1,29 @@
/*-------------------------------------------------------------------------
*
* 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 */

@ -0,0 +1,143 @@
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

@ -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

@ -0,0 +1,98 @@
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;
Loading…
Cancel
Save