mirror of https://github.com/postgres/postgres
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.compull/182/head
parent
6f782a2a17
commit
e839c8ecc9
@ -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); |
||||
} |
@ -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 |
@ -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…
Reference in new issue