postgresql/src/test/regress/sql/stats_import.sql
Michael Paquier 661095c40c Fix MCV input array checks in statistics restore functions
The SQL functions for the restore of attribute and expression statistics
accept "most_common_vals" and "most_common_freqs" as independent arrays.
The planner assumes these have the same number of elements, but it was
possible to insert in the catalogs data that would cause an over-read
when the catalog data is loaded in the planner.

There were two holes in the stats restore logic:
- Both arrays should match in size.
- The input array must be one-dimensional, and it should match with what
is delivered by pg_dump when scanning the pg_stats catalogs.

The multivariate extended statistics MCV path (import_mcv) already
validated these inputs via check_mcvlist_array(), and is not affected.
These problems exist in v18 and newer versions for the restore of
attribute statistics.  These problems affect only HEAD for the restore
of the expression statistics.

Reported-by: Jeroen Gui <jeroen.gui1@proton.me>
Author: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Reviewed-by: John Naylor <johncnaylorls@gmail.com>
Security: CVE-2026-6575
Backpatch-through: 18
2026-05-11 05:13:47 -07:00

1027 lines
30 KiB
PL/PgSQL

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[]
) WITH (autovacuum_enabled = false);
SELECT
pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relpages', 18::integer,
'reltuples', 21::real,
'relallvisible', 24::integer,
'relallfrozen', 27::integer);
-- CREATE INDEX on a table with autovac disabled should not overwrite
-- stats
CREATE INDEX test_i ON stats_import.test(id);
SELECT relname, relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass
ORDER BY relname;
SELECT pg_clear_relation_stats('stats_import', 'test');
--
-- relstats tests
--
-- error: schemaname missing
SELECT pg_catalog.pg_restore_relation_stats(
'relname', 'test',
'relpages', 17::integer);
-- error: relname missing
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relpages', 17::integer);
--- error: schemaname is wrong type
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 3.6::float,
'relname', 'test',
'relpages', 17::integer);
--- error: relname is wrong type
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 0::oid,
'relpages', 17::integer);
-- error: relation not found
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'relpages', 17::integer);
-- error: odd number of variadic arguments cannot be pairs
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible');
-- error: argument name is NULL
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
NULL, '17'::integer);
-- starting stats
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test_i'::regclass;
-- regular indexes have special case locking rules
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test_i',
'relpages', 18::integer);
SELECT mode FROM pg_locks
WHERE relation = 'stats_import.test'::regclass AND
pid = pg_backend_pid() AND granted;
SELECT mode FROM pg_locks
WHERE relation = 'stats_import.test_i'::regclass AND
pid = pg_backend_pid() AND granted;
COMMIT;
-- relpages may be -1 for partitioned tables
CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
CREATE TABLE stats_import.part_child_1
PARTITION OF stats_import.part_parent
FOR VALUES FROM (0) TO (10)
WITH (autovacuum_enabled = false);
CREATE INDEX part_parent_i ON stats_import.part_parent(i);
ANALYZE stats_import.part_parent;
SELECT relpages
FROM pg_class
WHERE oid = 'stats_import.part_parent'::regclass;
--
-- Partitioned indexes aren't analyzed but it is possible to set
-- stats. The locking rules are different from normal indexes due to
-- the rules for in-place updates: both the partitioned table and the
-- partitioned index are locked in ShareUpdateExclusive mode.
--
BEGIN;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'part_parent_i',
'relpages', 2::integer);
SELECT mode FROM pg_locks
WHERE relation = 'stats_import.part_parent'::regclass AND
pid = pg_backend_pid() AND granted;
SELECT mode FROM pg_locks
WHERE relation = 'stats_import.part_parent_i'::regclass AND
pid = pg_backend_pid() AND granted;
COMMIT;
SELECT relpages
FROM pg_class
WHERE oid = 'stats_import.part_parent_i'::regclass;
-- ok: set all relstats, with version, no bounds checking
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'version', 150000::integer,
'relpages', '-17'::integer,
'reltuples', 400::real,
'relallvisible', 4::integer,
'relallfrozen', 2::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relpages, rest stay same
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relpages', '16'::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: set just reltuples, rest stay same
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'reltuples', '500'::real);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: set just relallvisible, rest stay same
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relallvisible', 5::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: just relallfrozen
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'version', 150000::integer,
'relallfrozen', 3::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- warn: bad relpages type, rest updated
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relpages', 'nope'::text,
'reltuples', 400.0::real,
'relallvisible', 4::integer,
'relallfrozen', 3::integer);
SELECT relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- unrecognized argument name, rest ok
SELECT pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'test',
'relpages', '171'::integer,
'nope', 10::integer);
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- ok: clear stats
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test');
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- invalid relkinds for statistics
CREATE SEQUENCE stats_import.testseq;
SELECT pg_catalog.pg_restore_relation_stats(
'schemaname', 'stats_import',
'relname', 'testseq');
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq');
CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview');
--
-- attribute stats
--
-- error: schemaname missing
SELECT pg_catalog.pg_restore_attribute_stats(
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: schema does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'nope',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: relname missing
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: relname does not exist
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'nope',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: relname null
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', NULL,
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: NULL attname
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', NULL,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'nope',
'inherited', false::boolean,
'null_frac', 0.1::real,
'avg_width', 2::integer,
'n_distinct', 0.3::real);
-- error: both attname and attnum
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: neither attname nor attnum
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: attribute is system column
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'xmin',
'inherited', false::boolean,
'null_frac', 0.1::real);
-- error: inherited null
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', NULL::boolean,
'null_frac', 0.1::real);
-- ok: just the fixed values, with version, no stakinds
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'version', 150000::integer,
'null_frac', 0.2::real,
'avg_width', 5::integer,
'n_distinct', 0.6::real);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--
-- ok: restore by attnum, we normally reserve this for
-- indexes, but there is no reason it shouldn't work
-- for any stat-having relation.
--
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attnum', 1::smallint,
'inherited', false::boolean,
'null_frac', 0.4::real);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: unrecognized argument name, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.2::real,
'nope', 0.5::real);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: mcv / mcf null mismatch part 1, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_freqs', '{0.1,0.2,0.3}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: mcv / mcf null mismatch part 2, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.21::real,
'most_common_vals', '{1,2,3}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: mcf type mismatch, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.22::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.2,0.1}'::double precision[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: mcv cast failure, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.23::real,
'most_common_vals', '{2,four,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: mcv / mcf array length mismatch (more vals), mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: mcv / mcf array length mismatch (more freqs), mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.25::real,
'most_common_vals', '{2,1}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: most_common_vals is multi-dimensional, mcv-pair fails, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.26::real,
'most_common_vals', '{{2,1},{3,4}}'::text,
'most_common_freqs', '{0.3,0.25,0.05,0.04}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- ok: mcv+mcf
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'most_common_vals', '{2,1,3}'::text,
'most_common_freqs', '{0.3,0.25,0.05}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: NULL in histogram array, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.24::real,
'histogram_bounds', '{1,NULL,3,4}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- ok: histogram_bounds
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'histogram_bounds', '{1,2,3,4}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: elem_count_histogram null element, rest get set
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.25::real,
'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
-- ok: elem_count_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.26::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
-- warn: range stats on a scalar type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.27::real,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: range_empty_frac range_length_hist null mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.28::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.29::real,
'range_empty_frac', 0.5::real
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
-- ok: range_empty_frac + range_length_hist
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'arange',
'inherited', false::boolean,
'range_empty_frac', 0.5::real,
'range_length_histogram', '{399,499,Infinity}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
-- warn: range bounds histogram on scalar, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.31::real,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- ok: range_bounds_histogram
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'arange',
'inherited', false::boolean,
'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'arange',
'inherited', false::boolean,
'null_frac', 0.32::real,
'most_common_elems', '{3,1}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
-- warn: scalars can't have mcelem, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.33::real,
'most_common_elems', '{1,3}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
-- warn: mcelem / mcelem mismatch, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.34::real,
'most_common_elems', '{one,two}'::text
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
-- warn: mcelem / mcelem null mismatch part 2, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'tags',
'inherited', false::boolean,
'null_frac', 0.35::real,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
-- ok: mcelem
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'tags',
'inherited', false::boolean,
'most_common_elems', '{one,three}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'tags';
-- warn: scalars can't have elem_count_histogram, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test',
'attname', 'id',
'inherited', false::boolean,
'null_frac', 0.36::real,
'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[]
);
SELECT *
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'id';
--
-- Test the ability to exactly copy data from one table to an identical table,
-- correctly reconstructing the stakind order as well as the staopN and
-- stacollN values. Because oids are not stable across databases, we can only
-- test this when the source and destination are on the same database
-- instance. For that reason, we borrow and adapt a query found in fe_utils
-- and used by pg_dump/pg_upgrade.
--
INSERT INTO stats_import.test
SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_import.complex_type, int4range(1,4), array['red','green']
UNION ALL
SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_import.complex_type, int4range(1,4), array['blue','yellow']
UNION ALL
SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, int4range(-1,1), array['"orange"', 'purple', 'cyan']
UNION ALL
SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
-- 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));
--
-- Copy stats from test to test_clone, and is_odd to is_odd_clone
--
SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
FROM pg_catalog.pg_stats AS s
CROSS JOIN LATERAL
pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', s.tablename::text || '_clone',
'attname', s.attname::text,
'inherited', s.inherited,
'version', 150000,
'null_frac', s.null_frac,
'avg_width', s.avg_width,
'n_distinct', s.n_distinct,
'most_common_vals', s.most_common_vals::text,
'most_common_freqs', s.most_common_freqs,
'histogram_bounds', s.histogram_bounds::text,
'correlation', s.correlation,
'most_common_elems', s.most_common_elems::text,
'most_common_elem_freqs', s.most_common_elem_freqs,
'elem_count_histogram', s.elem_count_histogram,
'range_bounds_histogram', s.range_bounds_histogram::text,
'range_empty_frac', s.range_empty_frac,
'range_length_histogram', s.range_length_histogram::text) AS r
WHERE s.schemaname = 'stats_import'
AND s.tablename IN ('test', 'is_odd')
ORDER BY s.tablename, s.attname, s.inherited;
SELECT c.relname, COUNT(*) AS num_stats
FROM pg_class AS c
JOIN pg_statistic s ON s.starelid = c.oid
WHERE c.relnamespace = 'stats_import'::regnamespace
AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
GROUP BY c.relname
ORDER BY c.relname;
-- check test minus test_clone
SELECT
a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
s.stavalues5::text AS sv5, 'test' AS direction
FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.test'::regclass
EXCEPT
SELECT
a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
s.stavalues5::text AS sv5, 'test' AS direction
FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.test_clone'::regclass;
-- check test_clone minus test
SELECT
a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
s.stavalues5::text AS sv5, 'test_clone' AS direction
FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.test_clone'::regclass
EXCEPT
SELECT
a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
s.stavalues5::text AS sv5, 'test_clone' AS direction
FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.test'::regclass;
-- check is_odd minus is_odd_clone
SELECT
a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
s.stavalues5::text AS sv5, 'is_odd' AS direction
FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.is_odd'::regclass
EXCEPT
SELECT
a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
s.stavalues5::text AS sv5, 'is_odd' AS direction
FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
-- check is_odd_clone minus is_odd
SELECT
a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
EXCEPT
SELECT
a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
FROM pg_statistic s
JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
WHERE s.starelid = 'stats_import.is_odd'::regclass;
-- attribute stats exist before a clear, but not after
SELECT COUNT(*)
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
SELECT pg_catalog.pg_clear_attribute_stats(
schemaname => 'stats_import',
relname => 'test',
attname => 'arange',
inherited => false);
SELECT COUNT(*)
FROM pg_stats
WHERE schemaname = 'stats_import'
AND tablename = 'test'
AND inherited = false
AND attname = 'arange';
-- temp tables
CREATE TEMP TABLE stats_temp(i int);
SELECT pg_restore_relation_stats(
'schemaname', 'pg_temp',
'relname', 'stats_temp',
'relpages', '-19'::integer,
'reltuples', 401::real,
'relallvisible', 5::integer,
'relallfrozen', 3::integer);
SELECT relname, relpages, reltuples, relallvisible, relallfrozen
FROM pg_class
WHERE oid = 'pg_temp.stats_temp'::regclass
ORDER BY relname;
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'pg_temp',
'relname', 'stats_temp',
'attname', 'i',
'inherited', false::boolean,
'null_frac', 0.0123::real
);
SELECT tablename, null_frac
FROM pg_stats
WHERE schemaname like 'pg_temp%'
AND tablename = 'stats_temp'
AND inherited = false
AND attname = 'i';
DROP TABLE stats_temp;
DROP SCHEMA stats_import CASCADE;