diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml index 2896cd9e429..e7ea16f73b3 100644 --- a/doc/src/sgml/func/func-admin.sgml +++ b/doc/src/sgml/func/func-admin.sgml @@ -2165,6 +2165,35 @@ SELECT pg_restore_attribute_stats( + + + + + pg_clear_extended_stats + + pg_clear_extended_stats ( + schemaname name, + relname name, + statistics_schemaname name, + statistics_name name, + inherited boolean ) + void + + + Clears data of an extended statistics object, as though the object + was newly-created. The required arguments are + schemaname and relname to + specify the schema and table name of the relation whose statistics + are cleared, as well as statistics_schemaname + and statistics_name to specify the schema and + extended statistics name of the extended statistics object to clear. + + + The caller must have the MAINTAIN privilege on + the table or be the owner of the database. + + + diff --git a/src/backend/statistics/Makefile b/src/backend/statistics/Makefile index 4672bd90f22..7ff5938b027 100644 --- a/src/backend/statistics/Makefile +++ b/src/backend/statistics/Makefile @@ -16,6 +16,7 @@ OBJS = \ attribute_stats.o \ dependencies.o \ extended_stats.o \ + extended_stats_funcs.o \ mcv.o \ mvdistinct.o \ relation_stats.o \ diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c new file mode 100644 index 00000000000..22b9750ce19 --- /dev/null +++ b/src/backend/statistics/extended_stats_funcs.c @@ -0,0 +1,232 @@ +/*------------------------------------------------------------------------- + * + * extended_stats_funcs.c + * Functions for manipulating extended statistics. + * + * This file includes the set of facilities required to support the direct + * manipulations of extended statistics objects. + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/statistics/extended_stats_funcs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_database.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_statistic_ext_data.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "statistics/stat_utils.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + + +/* + * Index of the arguments for the SQL functions. + */ +enum extended_stats_argnum +{ + RELSCHEMA_ARG = 0, + RELNAME_ARG, + STATSCHEMA_ARG, + STATNAME_ARG, + INHERITED_ARG, + NUM_EXTENDED_STATS_ARGS, +}; + +/* + * The argument names and type OIDs of the arguments for the SQL + * functions. + */ +static struct StatsArgInfo extarginfo[] = +{ + [RELSCHEMA_ARG] = {"schemaname", TEXTOID}, + [RELNAME_ARG] = {"relname", TEXTOID}, + [STATSCHEMA_ARG] = {"statistics_schemaname", TEXTOID}, + [STATNAME_ARG] = {"statistics_name", TEXTOID}, + [INHERITED_ARG] = {"inherited", BOOLOID}, + [NUM_EXTENDED_STATS_ARGS] = {0}, +}; + +static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid, + const char *stxname); +static bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited); + +/* + * Fetch a pg_statistic_ext row by name and namespace OID. + */ +static HeapTuple +get_pg_statistic_ext(Relation pg_stext, Oid nspoid, const char *stxname) +{ + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + Oid stxoid = InvalidOid; + + ScanKeyInit(&key[0], + Anum_pg_statistic_ext_stxname, + BTEqualStrategyNumber, + F_NAMEEQ, + CStringGetDatum(stxname)); + ScanKeyInit(&key[1], + Anum_pg_statistic_ext_stxnamespace, + BTEqualStrategyNumber, + F_OIDEQ, + ObjectIdGetDatum(nspoid)); + + /* + * Try to find matching pg_statistic_ext row. + */ + scan = systable_beginscan(pg_stext, + StatisticExtNameIndexId, + true, + NULL, + 2, + key); + + /* Lookup is based on a unique index, so we get either 0 or 1 tuple. */ + tup = systable_getnext(scan); + + if (HeapTupleIsValid(tup)) + stxoid = ((Form_pg_statistic_ext) GETSTRUCT(tup))->oid; + + systable_endscan(scan); + + if (!OidIsValid(stxoid)) + return NULL; + + return SearchSysCacheCopy1(STATEXTOID, ObjectIdGetDatum(stxoid)); +} + +/* + * Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext + * row and "inherited" pair. + */ +static bool +delete_pg_statistic_ext_data(Oid stxoid, bool inherited) +{ + Relation sed = table_open(StatisticExtDataRelationId, RowExclusiveLock); + HeapTuple oldtup; + bool result = false; + + /* Is there already a pg_statistic tuple for this attribute? */ + oldtup = SearchSysCache2(STATEXTDATASTXOID, + ObjectIdGetDatum(stxoid), + BoolGetDatum(inherited)); + + if (HeapTupleIsValid(oldtup)) + { + CatalogTupleDelete(sed, &oldtup->t_self); + ReleaseSysCache(oldtup); + result = true; + } + + table_close(sed, RowExclusiveLock); + + CommandCounterIncrement(); + + return result; +} + +/* + * Delete statistics for the given statistics object. + */ +Datum +pg_clear_extended_stats(PG_FUNCTION_ARGS) +{ + char *relnspname; + char *relname; + char *nspname; + Oid nspoid; + Oid relid; + char *stxname; + bool inherited; + Relation pg_stext; + HeapTuple tup; + Form_pg_statistic_ext stxform; + Oid locked_table = InvalidOid; + + /* relation arguments */ + stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG); + relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG)); + stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG); + relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG)); + + /* extended statistics arguments */ + stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG); + nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG)); + stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG); + stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG)); + stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG); + inherited = PG_GETARG_BOOL(INHERITED_ARG); + + if (RecoveryInProgress()) + { + ereport(WARNING, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("recovery is in progress"), + errhint("Statistics cannot be modified during recovery.")); + PG_RETURN_VOID(); + } + + /* + * First open the relation where we expect to find the statistics. This + * is similar to relation and attribute statistics, so as ACL checks are + * done before any locks are taken, even before any attempts related to + * the extended stats object. + */ + relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1), + ShareUpdateExclusiveLock, 0, + RangeVarCallbackForStats, &locked_table); + + /* Now check if the namespace of the stats object exists. */ + nspoid = get_namespace_oid(nspname, true); + if (nspoid == InvalidOid) + { + ereport(WARNING, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find schema \"%s\"", nspname)); + PG_RETURN_VOID(); + } + + pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock); + tup = get_pg_statistic_ext(pg_stext, nspoid, stxname); + + if (!HeapTupleIsValid(tup)) + { + table_close(pg_stext, RowExclusiveLock); + ereport(WARNING, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find extended statistics object \"%s\".\"%s\"", + nspname, stxname)); + PG_RETURN_VOID(); + } + + stxform = (Form_pg_statistic_ext) GETSTRUCT(tup); + + /* + * This should be consistent, based on the lock taken on the table when we + * started. + */ + if (stxform->stxrelid != relid) + elog(ERROR, "cache lookup failed for extended stats %u: found relation %u but expected %u", + stxform->oid, stxform->stxrelid, relid); + + delete_pg_statistic_ext_data(stxform->oid, inherited); + heap_freetuple(tup); + + table_close(pg_stext, RowExclusiveLock); + + PG_RETURN_VOID(); +} diff --git a/src/backend/statistics/meson.build b/src/backend/statistics/meson.build index 5c89f869812..9a7bf55e301 100644 --- a/src/backend/statistics/meson.build +++ b/src/backend/statistics/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'attribute_stats.c', 'dependencies.c', 'extended_stats.c', + 'extended_stats_funcs.c', 'mcv.c', 'mvdistinct.c', 'relation_stats.c', diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index ab38a69f0f8..13ac6be613e 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202601081 +#define CATALOG_VERSION_NO 202601161 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 2ac69bf2df5..894b6a1b6d6 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12618,6 +12618,13 @@ proname => 'gist_translate_cmptype_common', prorettype => 'int2', proargtypes => 'int4', prosrc => 'gist_translate_cmptype_common' }, +# Extended Statistics functions +{ oid => '9948', descr => 'clear statistics on extended statistics object', + proname => 'pg_clear_extended_stats', proisstrict => 'f', provolatile => 'v', + proparallel => 'u', prorettype => 'void', proargtypes => 'text text text text bool', + proargnames => '{schemaname,relname,statistics_schemaname,statistics_name,inherited}', + prosrc => 'pg_clear_extended_stats' }, + # AIO related functions { oid => '6399', descr => 'information about in-progress asynchronous IOs', proname => 'pg_get_aios', prorows => '100', proretset => 't', diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index 98ce7dc2841..68ddd619edb 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -132,8 +132,36 @@ CREATE TABLE stats_import.part_child_1 PARTITION OF stats_import.part_parent FOR VALUES FROM (0) TO (10) WITH (autovacuum_enabled = false); +-- This ensures the presence of extended statistics marked with +-- inherited = true. +CREATE STATISTICS stats_import.part_parent_stat + ON i, (i % 2) + FROM stats_import.part_parent; CREATE INDEX part_parent_i ON stats_import.part_parent(i); +INSERT INTO stats_import.part_parent +SELECT g.g +FROM generate_series(0,9) AS g(g); +SELECT COUNT(*) FROM stats_import.part_parent; + count +------- + 10 +(1 row) + +SELECT COUNT(*) FROM stats_import.part_child_1; + count +------- + 10 +(1 row) + ANALYZE stats_import.part_parent; +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'part_parent_stat' GROUP BY e.inherited; + count | inherited +-------+----------- + 1 | t +(1 row) + SELECT relpages FROM pg_class WHERE oid = 'stats_import.part_parent'::regclass; @@ -1084,11 +1112,17 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, UNION ALL SELECT 4, 'four', NULL, int4range(0,100), NULL; CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1)); +CREATE STATISTICS stats_import.test_stat + ON name, comp, lower(arange), array_length(tags,1) + FROM stats_import.test; -- Generate statistics on table with data ANALYZE stats_import.test; CREATE TABLE stats_import.test_clone ( LIKE stats_import.test ) WITH (autovacuum_enabled = false); CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1)); +CREATE STATISTICS stats_import.test_stat_clone + ON name, comp, lower(arange), array_length(tags,1) + FROM stats_import.test_clone; -- -- Copy stats from test to test_clone, and is_odd to is_odd_clone -- @@ -1342,6 +1376,182 @@ AND attname = 'i'; (1 row) DROP TABLE stats_temp; +-- Tests for pg_clear_extended_stats(). +-- Invalid argument values. +SELECT pg_clear_extended_stats(schemaname => NULL, + relname => 'rel_foo', + statistics_schemaname => 'schema_foo', + statistics_name => 'stat_bar', + inherited => false); +ERROR: argument "schemaname" must not be null +SELECT pg_clear_extended_stats(schemaname => 'schema_foo', + relname => NULL, + statistics_schemaname => 'schema_foo', + statistics_name => 'stat_bar', + inherited => false); +ERROR: argument "relname" must not be null +SELECT pg_clear_extended_stats(schemaname => 'schema_foo', + relname => 'rel_foo', + statistics_schemaname => NULL, + statistics_name => 'stat_bar', + inherited => false); +ERROR: argument "statistics_schemaname" must not be null +SELECT pg_clear_extended_stats(schemaname => 'schema_foo', + relname => 'rel_foo', + statistics_schemaname => 'schema_foo', + statistics_name => NULL, + inherited => false); +ERROR: argument "statistics_name" must not be null +SELECT pg_clear_extended_stats(schemaname => 'schema_foo', + relname => 'rel_foo', + statistics_schemaname => 'schema_foo', + statistics_name => 'stat_bar', + inherited => NULL); +ERROR: argument "inherited" must not be null +-- Missing objects +SELECT pg_clear_extended_stats(schemaname => 'schema_not_exist', + relname => 'test', + statistics_schemaname => 'schema_not_exist', + statistics_name => 'test_stat', + inherited => false); +ERROR: schema "schema_not_exist" does not exist +SELECT pg_clear_extended_stats(schemaname => 'stats_import', + relname => 'table_not_exist', + statistics_schemaname => 'stats_import', + statistics_name => 'test_stat', + inherited => false); +ERROR: relation "stats_import.table_not_exist" does not exist +SELECT pg_clear_extended_stats(schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'schema_not_exist', + statistics_name => 'test_stat', + inherited => false); +WARNING: could not find schema "schema_not_exist" + pg_clear_extended_stats +------------------------- + +(1 row) + +SELECT pg_clear_extended_stats(schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'stats_import', + statistics_name => 'ext_stats_not_exist', + inherited => false); +WARNING: could not find extended statistics object "stats_import"."ext_stats_not_exist" + pg_clear_extended_stats +------------------------- + +(1 row) + +-- Check that records are removed after a valid clear call. +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat' GROUP BY e.inherited; + count | inherited +-------+----------- + 1 | f +(1 row) + +SELECT COUNT(*), e.inherited FROM pg_stats_ext_exprs AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat' GROUP BY e.inherited; + count | inherited +-------+----------- + 2 | f +(1 row) + +BEGIN; +SELECT pg_catalog.pg_clear_extended_stats( + schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'stats_import', + statistics_name => 'test_stat', + inherited => false); + pg_clear_extended_stats +------------------------- + +(1 row) + +SELECT mode FROM pg_locks WHERE locktype = 'relation' AND + relation = 'stats_import.test'::regclass AND + pid = pg_backend_pid(); + mode +-------------------------- + ShareUpdateExclusiveLock +(1 row) + +COMMIT; +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat' GROUP BY e.inherited; + count | inherited +-------+----------- +(0 rows) + +SELECT COUNT(*), e.inherited FROM pg_stats_ext_exprs AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat' GROUP BY e.inherited; + count | inherited +-------+----------- + 2 | +(1 row) + +-- And before/after on inherited stats +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'part_parent_stat' GROUP BY e.inherited; + count | inherited +-------+----------- + 1 | t +(1 row) + +SELECT pg_catalog.pg_clear_extended_stats( + schemaname => 'stats_import', + relname => 'part_parent', + statistics_schemaname => 'stats_import', + statistics_name => 'part_parent_stat', + inherited => true); + pg_clear_extended_stats +------------------------- + +(1 row) + +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'part_parent_stat' GROUP BY e.inherited; + count | inherited +-------+----------- +(0 rows) + +-- Check that MAINTAIN is required when clearing statistics. +CREATE ROLE regress_test_extstat_clear; +GRANT ALL ON SCHEMA stats_import TO regress_test_extstat_clear; +SET ROLE regress_test_extstat_clear; +SELECT pg_catalog.pg_clear_extended_stats( + schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'stats_import', + statistics_name => 'test_stat', + inherited => false); +ERROR: permission denied for table test +RESET ROLE; +GRANT MAINTAIN ON stats_import.test TO regress_test_extstat_clear; +SET ROLE regress_test_extstat_clear; +SELECT pg_catalog.pg_clear_extended_stats( + schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'stats_import', + statistics_name => 'test_stat', + inherited => false); + pg_clear_extended_stats +------------------------- + +(1 row) + +RESET ROLE; +REVOKE MAINTAIN ON stats_import.test FROM regress_test_extstat_clear; +REVOKE ALL ON SCHEMA stats_import FROM regress_test_extstat_clear; +DROP ROLE regress_test_extstat_clear; DROP SCHEMA stats_import CASCADE; NOTICE: drop cascades to 6 other objects DETAIL: drop cascades to type stats_import.complex_type diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index d140733a750..04d15202e65 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -108,10 +108,27 @@ CREATE TABLE stats_import.part_child_1 FOR VALUES FROM (0) TO (10) WITH (autovacuum_enabled = false); +-- This ensures the presence of extended statistics marked with +-- inherited = true. +CREATE STATISTICS stats_import.part_parent_stat + ON i, (i % 2) + FROM stats_import.part_parent; + CREATE INDEX part_parent_i ON stats_import.part_parent(i); +INSERT INTO stats_import.part_parent +SELECT g.g +FROM generate_series(0,9) AS g(g); + +SELECT COUNT(*) FROM stats_import.part_parent; +SELECT COUNT(*) FROM stats_import.part_child_1; + ANALYZE stats_import.part_parent; +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'part_parent_stat' GROUP BY e.inherited; + SELECT relpages FROM pg_class WHERE oid = 'stats_import.part_parent'::regclass; @@ -766,6 +783,10 @@ SELECT 4, 'four', NULL, int4range(0,100), NULL; CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1)); +CREATE STATISTICS stats_import.test_stat + ON name, comp, lower(arange), array_length(tags,1) + FROM stats_import.test; + -- Generate statistics on table with data ANALYZE stats_import.test; @@ -774,6 +795,10 @@ CREATE TABLE stats_import.test_clone ( LIKE stats_import.test ) CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1)); +CREATE STATISTICS stats_import.test_stat_clone + ON name, comp, lower(arange), array_length(tags,1) + FROM stats_import.test_clone; + -- -- Copy stats from test to test_clone, and is_odd to is_odd_clone -- @@ -970,4 +995,116 @@ AND tablename = 'stats_temp' AND inherited = false AND attname = 'i'; DROP TABLE stats_temp; + +-- Tests for pg_clear_extended_stats(). +-- Invalid argument values. +SELECT pg_clear_extended_stats(schemaname => NULL, + relname => 'rel_foo', + statistics_schemaname => 'schema_foo', + statistics_name => 'stat_bar', + inherited => false); +SELECT pg_clear_extended_stats(schemaname => 'schema_foo', + relname => NULL, + statistics_schemaname => 'schema_foo', + statistics_name => 'stat_bar', + inherited => false); +SELECT pg_clear_extended_stats(schemaname => 'schema_foo', + relname => 'rel_foo', + statistics_schemaname => NULL, + statistics_name => 'stat_bar', + inherited => false); +SELECT pg_clear_extended_stats(schemaname => 'schema_foo', + relname => 'rel_foo', + statistics_schemaname => 'schema_foo', + statistics_name => NULL, + inherited => false); +SELECT pg_clear_extended_stats(schemaname => 'schema_foo', + relname => 'rel_foo', + statistics_schemaname => 'schema_foo', + statistics_name => 'stat_bar', + inherited => NULL); +-- Missing objects +SELECT pg_clear_extended_stats(schemaname => 'schema_not_exist', + relname => 'test', + statistics_schemaname => 'schema_not_exist', + statistics_name => 'test_stat', + inherited => false); +SELECT pg_clear_extended_stats(schemaname => 'stats_import', + relname => 'table_not_exist', + statistics_schemaname => 'stats_import', + statistics_name => 'test_stat', + inherited => false); +SELECT pg_clear_extended_stats(schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'schema_not_exist', + statistics_name => 'test_stat', + inherited => false); +SELECT pg_clear_extended_stats(schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'stats_import', + statistics_name => 'ext_stats_not_exist', + inherited => false); + +-- Check that records are removed after a valid clear call. +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat' GROUP BY e.inherited; +SELECT COUNT(*), e.inherited FROM pg_stats_ext_exprs AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat' GROUP BY e.inherited; +BEGIN; +SELECT pg_catalog.pg_clear_extended_stats( + schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'stats_import', + statistics_name => 'test_stat', + inherited => false); +SELECT mode FROM pg_locks WHERE locktype = 'relation' AND + relation = 'stats_import.test'::regclass AND + pid = pg_backend_pid(); +COMMIT; +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat' GROUP BY e.inherited; +SELECT COUNT(*), e.inherited FROM pg_stats_ext_exprs AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat' GROUP BY e.inherited; +-- And before/after on inherited stats +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'part_parent_stat' GROUP BY e.inherited; +SELECT pg_catalog.pg_clear_extended_stats( + schemaname => 'stats_import', + relname => 'part_parent', + statistics_schemaname => 'stats_import', + statistics_name => 'part_parent_stat', + inherited => true); +SELECT COUNT(*), e.inherited FROM pg_stats_ext AS e + WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'part_parent_stat' GROUP BY e.inherited; + +-- Check that MAINTAIN is required when clearing statistics. +CREATE ROLE regress_test_extstat_clear; +GRANT ALL ON SCHEMA stats_import TO regress_test_extstat_clear; +SET ROLE regress_test_extstat_clear; +SELECT pg_catalog.pg_clear_extended_stats( + schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'stats_import', + statistics_name => 'test_stat', + inherited => false); +RESET ROLE; +GRANT MAINTAIN ON stats_import.test TO regress_test_extstat_clear; +SET ROLE regress_test_extstat_clear; +SELECT pg_catalog.pg_clear_extended_stats( + schemaname => 'stats_import', + relname => 'test', + statistics_schemaname => 'stats_import', + statistics_name => 'test_stat', + inherited => false); +RESET ROLE; +REVOKE MAINTAIN ON stats_import.test FROM regress_test_extstat_clear; +REVOKE ALL ON SCHEMA stats_import FROM regress_test_extstat_clear; +DROP ROLE regress_test_extstat_clear; + DROP SCHEMA stats_import CASCADE;