1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Fix import of statistics for domains over [multi]range types and tsvector

pg_restore_attribute_stats() rejected range_length_histogram,
range_empty_frac and range_bounds_histogram for a column whose type is a
domain over a range or a multirange type.  The same issue existed for
most_common_elems and elem_count_histogram for a domain over tsvector.
pg_restore_extended_stats() had the same set of problems for expressions
whose type is a domain over a range, a multirange type, or tsvector.

ANALYZE is able to generate such stats, incorporating the knowledge to
handle domains in the in-core typanalyze callbacks.

This problem is resolved by being more aggressive with the fetch of the
base type of a domain in the early phases of restore for attribute and
extended stats (special tip to Jian He for pointing out the unnecessary
typcache lookups), reflecting on the surrounding helper routines shared
by both code paths.  The fix for v18 is local to attribute_stats.c, as
only attribute stats need to be touched.

Tests are included in a fashion consistent with the surroundings.  The
consequence of this issue was the rejection of stats that ANALYZE was
able to build, which was not critical but annoying as it would lead to a
gap in the stats restored.

Reported-by: Qifan Liu <imchifan@163.com>
Author: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Jian He <jian.universality@gmail.com>
Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
Discussion: https://postgr.es/m/19715-b8be35083016f289@postgresql.org
Backpatch-through: 18
This commit is contained in:
Michael Paquier
2026-09-26 09:28:15 +09:00
parent d26295f12e
commit aa60617b69
6 changed files with 662 additions and 46 deletions

View File

@@ -221,7 +221,7 @@ attribute_statistics_update_internal(Oid reloid,
Oid atttypid = InvalidOid;
int32 atttypmod;
char atttyptype;
TypeCacheEntry *basetypcache;
Oid atttypcoll = InvalidOid;
Oid eq_opr = InvalidOid;
Oid lt_opr = InvalidOid;
@@ -229,6 +229,8 @@ attribute_statistics_update_internal(Oid reloid,
Oid elemtypid = InvalidOid;
Oid elem_eq_opr = InvalidOid;
Oid bounds_typid = InvalidOid;
FmgrInfo array_in_fn;
bool do_mcv = !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
@@ -296,14 +298,13 @@ attribute_statistics_update_internal(Oid reloid,
/* derive information from attribute */
statatt_get_type(reloid, attnum,
&atttypid, &atttypmod,
&atttyptype, &atttypcoll,
&basetypcache, &atttypcoll,
&eq_opr, &lt_opr);
/* if needed, derive element type */
if (do_mcelem || do_dechist)
{
if (!statatt_get_elem_type(atttypid, atttyptype,
&elemtypid, &elem_eq_opr))
if (!statatt_get_elem_type(basetypcache, &elemtypid, &elem_eq_opr))
{
ereport(WARNING,
(errmsg("could not determine element type of column \"%s\"", attname),
@@ -334,7 +335,7 @@ attribute_statistics_update_internal(Oid reloid,
/* only range types can have range stats */
if ((do_range_length_histogram || do_bounds_histogram) &&
!(atttyptype == TYPTYPE_RANGE || atttyptype == TYPTYPE_MULTIRANGE))
!statatt_get_range_type(basetypcache, &bounds_typid))
{
ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -498,14 +499,8 @@ attribute_statistics_update_internal(Oid reloid,
{
bool converted = false;
Datum stavalues;
Oid bounds_typid = atttypid;
/*
* If it's a multirange, step down to the range type, as is done by
* multirange_typanalyze().
*/
if (type_is_multirange(atttypid))
bounds_typid = get_multirange_range(atttypid);
Assert(OidIsValid(bounds_typid));
stavalues = statatt_build_stavalues("range_bounds_histogram",
&array_in_fn,

View File

@@ -1115,7 +1115,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
bool *pg_statistic_ok)
{
const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
TypeCacheEntry *typcache;
TypeCacheEntry *basetypcache;
Datum values[Natts_pg_statistic];
bool nulls[Natts_pg_statistic];
bool replaces[Natts_pg_statistic];
@@ -1123,6 +1123,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
Datum pgstdat = (Datum) 0;
Oid elemtypid = InvalidOid;
Oid elemeqopr = InvalidOid;
Oid rtypid = InvalidOid;
bool found[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
JsonbValue val[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
@@ -1221,7 +1222,13 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
}
/* This finds the right operators even if atttypid is a domain */
typcache = lookup_type_cache(typid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
basetypcache = lookup_type_cache(typid, TYPECACHE_LT_OPR |
TYPECACHE_EQ_OPR |
TYPECACHE_DOMAIN_BASE_INFO);
if (OidIsValid(basetypcache->domainBaseType))
basetypcache = lookup_type_cache(basetypcache->domainBaseType,
TYPECACHE_LT_OPR |
TYPECACHE_EQ_OPR);
statatt_init_empty_tuple(InvalidOid, InvalidAttrNumber, false,
values, nulls, replaces);
@@ -1230,7 +1237,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
* Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
* compute_tsvector_stats().
*/
if (typid == TSVECTOROID)
if (basetypcache->type_id == TSVECTOROID)
typcoll = DEFAULT_COLLATION_OID;
/*
@@ -1240,8 +1247,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
*/
if (found[MOST_COMMON_ELEMS_ELEM] || found[ELEM_COUNT_HISTOGRAM_ELEM])
{
if (!statatt_get_elem_type(typid, typcache->typtype,
&elemtypid, &elemeqopr))
if (!statatt_get_elem_type(basetypcache, &elemtypid, &elemeqopr))
{
ereport(WARNING,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1259,8 +1265,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
found[RANGE_EMPTY_FRAC_ELEM] ||
found[RANGE_BOUNDS_HISTOGRAM_ELEM])
{
if (typcache->typtype != TYPTYPE_RANGE &&
typcache->typtype != TYPTYPE_MULTIRANGE)
if (!statatt_get_range_type(basetypcache, &rtypid))
{
ereport(WARNING,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1364,7 +1369,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
statatt_set_slot(values, nulls, replaces,
STATISTIC_KIND_MCV,
typcache->eq_opr, typcoll,
basetypcache->eq_opr, typcoll,
stanumbers, false, stavalues, false);
}
else
@@ -1386,7 +1391,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
if (val_ok)
statatt_set_slot(values, nulls, replaces,
STATISTIC_KIND_HISTOGRAM,
typcache->lt_opr, typcoll,
basetypcache->lt_opr, typcoll,
0, true, stavalues, false);
else
goto pg_statistic_error;
@@ -1405,7 +1410,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
statatt_set_slot(values, nulls, replaces,
STATISTIC_KIND_CORRELATION,
typcache->lt_opr, typcoll,
basetypcache->lt_opr, typcoll,
stanumbers, false, 0, true);
}
else
@@ -1476,14 +1481,8 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
Datum stavalues;
bool val_ok = false;
char *s;
Oid rtypid = typid;
/*
* If it's a multirange, step down to the range type, as is done by
* multirange_typanalyze().
*/
if (type_is_multirange(typid))
rtypid = get_multirange_range(typid);
Assert(OidIsValid(rtypid));
s = jbv_string_get_cstr(&val[RANGE_BOUNDS_HISTOGRAM_ELEM]);

View File

@@ -434,6 +434,9 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
* This duplicates the logic in examine_attribute() but it will not skip the
* attribute if the attstattarget is 0.
*
* *atttypid and *atttypmod describe the type as declared. *basetypcache is
* the cache entry of the base type behind any domain.
*
* This information, retrieved from pg_attribute and pg_type with some
* specific handling for index expressions, is a prerequisite to calling
* any of the other statatt_*() functions.
@@ -441,14 +444,13 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
void
statatt_get_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
TypeCacheEntry **basetypcache, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr)
{
Relation rel = relation_open(reloid, AccessShareLock);
Form_pg_attribute attr;
HeapTuple atup;
Node *expr;
TypeCacheEntry *typcache;
atup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(reloid),
Int16GetDatum(attnum));
@@ -495,35 +497,42 @@ statatt_get_type(Oid reloid, AttrNumber attnum,
ReleaseSysCache(atup);
/* finds the right operators even if atttypid is a domain */
typcache = lookup_type_cache(*atttypid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
*atttyptype = typcache->typtype;
*eq_opr = typcache->eq_opr;
*lt_opr = typcache->lt_opr;
*basetypcache = lookup_type_cache(*atttypid, TYPECACHE_LT_OPR |
TYPECACHE_EQ_OPR |
TYPECACHE_DOMAIN_BASE_INFO);
if (OidIsValid((*basetypcache)->domainBaseType))
*basetypcache = lookup_type_cache((*basetypcache)->domainBaseType,
TYPECACHE_LT_OPR |
TYPECACHE_EQ_OPR);
*eq_opr = (*basetypcache)->eq_opr;
*lt_opr = (*basetypcache)->lt_opr;
/*
* Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
* compute_tsvector_stats().
*/
if (*atttypid == TSVECTOROID)
if ((*basetypcache)->type_id == TSVECTOROID)
*atttypcoll = DEFAULT_COLLATION_OID;
relation_close(rel, NoLock);
}
/*
* Derive element type information from the attribute type. This information
* is needed when the given type is one that contains elements of other types.
* Derive element type information from the base type of an attribute. This
* information is needed when the given type is one that contains elements of
* other types.
*
* The atttypid and atttyptype should be derived from a previous call to
* The type cache entry should be derived from a previous call to
* statatt_get_type().
*/
bool
statatt_get_elem_type(Oid atttypid, char atttyptype,
statatt_get_elem_type(TypeCacheEntry *basetypcache,
Oid *elemtypid, Oid *elem_eq_opr)
{
TypeCacheEntry *elemtypcache;
if (atttypid == TSVECTOROID)
if (basetypcache->type_id == TSVECTOROID)
{
/*
* Special case: element type for tsvector is text. See
@@ -533,8 +542,8 @@ statatt_get_elem_type(Oid atttypid, char atttyptype,
}
else
{
/* find underlying element type through any domain */
*elemtypid = get_base_element_type(atttypid);
/* find the underlying element type */
*elemtypid = get_element_type(basetypcache->type_id);
}
if (!OidIsValid(*elemtypid))
@@ -550,6 +559,33 @@ statatt_get_elem_type(Oid atttypid, char atttyptype,
return true;
}
/*
* Derive the range type to use from the attribute type, returning false if
* the attribute cannot have range statistics at all.
*
* For a multirange type, we step down to its range type, because
* compute_range_stats() stores range bounds even when analyzing a multirange
* column (see also range_typanalyze() and multirange_typanalyze()).
*
* The type cache entry should be derived from a previous call to
* statatt_get_type(), so that any domain has already been looked through.
*/
bool
statatt_get_range_type(TypeCacheEntry *basetypcache, Oid *rangetypid)
{
if (basetypcache->typtype == TYPTYPE_MULTIRANGE)
*rangetypid = get_multirange_range(basetypcache->type_id);
else if (basetypcache->typtype == TYPTYPE_RANGE)
*rangetypid = basetypcache->type_id;
else
{
*rangetypid = InvalidOid;
return false;
}
return true;
}
/*
* Build an array with element type typid from a text datum, used as
* value of an attribute in a tuple to-be-inserted into pg_statistic.

View File

@@ -19,6 +19,9 @@
/* avoid including primnodes.h here */
typedef struct RangeVar RangeVar;
/* avoid including typcache.h here */
typedef struct TypeCacheEntry TypeCacheEntry;
struct StatsArgInfo
{
const char *argname;
@@ -43,7 +46,7 @@ extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
extern void statatt_get_type(Oid reloid, AttrNumber attnum,
Oid *atttypid, int32 *atttypmod,
char *atttyptype, Oid *atttypcoll,
TypeCacheEntry **basetypcache, Oid *atttypcoll,
Oid *eq_opr, Oid *lt_opr);
extern void statatt_init_empty_tuple(Oid reloid, int16 attnum, bool inherited,
Datum *values, bool *nulls, bool *replaces);
@@ -55,7 +58,9 @@ extern void statatt_set_slot(Datum *values, bool *nulls, bool *replaces,
extern Datum statatt_build_stavalues(const char *staname, FmgrInfo *array_in, Datum d,
Oid typid, int32 typmod, bool *ok);
extern bool statatt_get_elem_type(Oid atttypid, char atttyptype,
extern bool statatt_get_elem_type(TypeCacheEntry *basetypcache,
Oid *elemtypid, Oid *elem_eq_opr);
extern bool statatt_get_range_type(TypeCacheEntry *basetypcache,
Oid *rangetypid);
#endif /* STATS_UTILS_H */

View File

@@ -1467,6 +1467,231 @@ SELECT pg_catalog.pg_restore_attribute_stats(
t
(1 row)
-- test for domains over range and multirange types
CREATE DOMAIN stats_import.dom_int4 AS int4;
CREATE DOMAIN stats_import.dom_range AS int4range;
CREATE DOMAIN stats_import.dom_mrange AS int4multirange;
CREATE TABLE stats_import.test_dom(
id stats_import.dom_int4,
drange stats_import.dom_range,
dmrange stats_import.dom_mrange
) WITH (autovacuum_enabled = false);
INSERT INTO stats_import.test_dom
VALUES (1, '[1,3)', '{[1,3),[5,9),[20,30)}'),
(2, '[5,9)', '{[11,13),[15,19),[20,30)}'),
(3, '[11,15)', '{[21,23),[25,29),[120,130)}');
-- warn: domain over a scalar type cannot have range stats
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'attname', 'id',
'inherited', false,
'null_frac', 0.25::real,
'range_length_histogram', '{2,4,4}'::text,
'range_empty_frac', '0'::real,
'range_bounds_histogram', '{"[1,3)","[5,9)","[11,15)"}'::text
);
WARNING: column "id" is not a range type
DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
pg_restore_attribute_stats
----------------------------
f
(1 row)
SELECT *
FROM stats_import.pg_stats_stable
WHERE schemaname = 'stats_import'
AND tablename = 'test_dom'
AND inherited = false
AND attname = 'id';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
stats_import | test_dom | id | f | 0.25 | 0 | 0 | | | | | | | | | |
(1 row)
-- ok: range stats for a domain over a range type
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'attname', 'drange',
'inherited', false,
'range_length_histogram', '{2,4,4}'::text,
'range_empty_frac', '0'::real,
'range_bounds_histogram', '{"[1,3)","[5,9)","[11,15)"}'::text
);
pg_restore_attribute_stats
----------------------------
t
(1 row)
SELECT *
FROM stats_import.pg_stats_stable
WHERE schemaname = 'stats_import'
AND tablename = 'test_dom'
AND inherited = false
AND attname = 'drange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+-----------------------------
stats_import | test_dom | drange | f | 0 | 0 | 0 | | | | | | | | {2,4,4} | 0 | {"[1,3)","[5,9)","[11,15)"}
(1 row)
-- ok: range stats for a domain over a multirange type.
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'attname', 'dmrange',
'inherited', false,
'range_length_histogram', '{29,29,109}'::text,
'range_empty_frac', '0'::real,
'range_bounds_histogram', '{"[1,30)","[11,30)","[21,130)"}'::text
);
pg_restore_attribute_stats
----------------------------
t
(1 row)
SELECT *
FROM stats_import.pg_stats_stable
WHERE schemaname = 'stats_import'
AND tablename = 'test_dom'
AND inherited = false
AND attname = 'dmrange';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+---------------------------------
stats_import | test_dom | dmrange | f | 0 | 0 | 0 | | | | | | | | {29,29,109} | 0 | {"[1,30)","[11,30)","[21,130)"}
(1 row)
-- warn: multirange values in the bounds histogram of a domain. These
-- must be ranges.
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'attname', 'dmrange',
'inherited', false,
'range_length_histogram', '{29,29,109}'::text,
'range_empty_frac', '0'::real,
'range_bounds_histogram', '{"{[1,30)}","{[11,30)}"}'::text
);
WARNING: malformed range literal: "{[1,30)}"
DETAIL: Missing left parenthesis or bracket.
pg_restore_attribute_stats
----------------------------
f
(1 row)
--
-- Check that the range stats that ANALYZE generates for domains over range
-- and multirange types can be restored exactly.
--
ANALYZE stats_import.test_dom;
CREATE TABLE stats_import.test_dom_clone ( LIKE stats_import.test_dom )
WITH (autovacuum_enabled = false);
SELECT 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', 'test_dom_clone',
'attname', s.attname::text,
'inherited', s.inherited,
'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,
'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 = 'test_dom'
ORDER BY s.attname, s.inherited;
attname | inherited | r
---------+-----------+---
dmrange | f | t
drange | f | t
id | f | t
(3 rows)
SELECT relname, (stats).*
FROM stats_import.pg_statistic_get_difference('test_dom', 'test_dom_clone')
\gx
(0 rows)
-- test for a domain over tsvector.
CREATE DOMAIN stats_import.dom_tsvector AS tsvector;
CREATE TABLE stats_import.test_dom_ts(
id int,
v stats_import.dom_tsvector
) WITH (autovacuum_enabled = false);
INSERT INTO stats_import.test_dom_ts
SELECT g, to_tsvector('english', 'the quick brown fox ' || g)
FROM generate_series(1, 20) AS g;
-- ok: mcelem and elem_count_histogram for a domain over tsvector
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom_ts',
'attname', 'v',
'inherited', false,
'most_common_elems', '{brown,fox,quick}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
'elem_count_histogram', '{4,4,4,4,4,4,4,4,4,4}'::real[]);
pg_restore_attribute_stats
----------------------------
t
(1 row)
SELECT *
FROM stats_import.pg_stats_stable
WHERE schemaname = 'stats_import'
AND tablename = 'test_dom_ts'
AND inherited = false
AND attname = 'v';
schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
--------------+-------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-----------------------+------------------------+------------------+------------------------
stats_import | test_dom_ts | v | f | 0 | 0 | 0 | | | | | {brown,fox,quick} | {0.3,0.2,0.2,0.3,0} | {4,4,4,4,4,4,4,4,4,4} | | |
(1 row)
--
-- Check that the statistics that ANALYZE generates for a domain over
-- tsvector can be restored exactly.
--
ANALYZE stats_import.test_dom_ts;
CREATE TABLE stats_import.test_dom_ts_clone ( LIKE stats_import.test_dom_ts )
WITH (autovacuum_enabled = false);
SELECT 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', 'test_dom_ts_clone',
'attname', s.attname::text,
'inherited', s.inherited,
'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) AS r
WHERE s.schemaname = 'stats_import'
AND s.tablename = 'test_dom_ts'
ORDER BY s.attname, s.inherited;
attname | inherited | r
---------+-----------+---
id | f | t
v | f | t
(2 rows)
SELECT relname, (stats).*
FROM stats_import.pg_statistic_get_difference('test_dom_ts', 'test_dom_ts_clone')
\gx
(0 rows)
--
-- 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
@@ -2544,6 +2769,103 @@ range_length_histogram | {10179,10189,10199}
range_empty_frac | 0
range_bounds_histogram | {"[1,10200)","[11,10200)","[21,10200)"}
-- Check import of range stats for expressions whose type is a domain over a
-- range or a multirange type.
CREATE STATISTICS stats_import.test_dom_stat
ON id,
(range_merge(drange, drange)::stats_import.dom_range),
((dmrange + '{}'::int4multirange)::stats_import.dom_mrange)
FROM stats_import.test_dom;
-- warn: reject multirange values in the bounds histogram of a domain. These
-- must be ranges.
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'statistics_schemaname', 'stats_import',
'statistics_name', 'test_dom_stat',
'inherited', false,
'exprs', '[{"range_length_histogram": "{2,4,4}",
"range_empty_frac": "0",
"range_bounds_histogram": "{\"[1,3)\",\"[5,9)\",\"[11,15)\"}"},
{"range_length_histogram": "{29,29,109}",
"range_empty_frac": "0",
"range_bounds_histogram": "{\"{[1,30)}\",\"{[11,30)}\"}"}]'::jsonb);
WARNING: malformed range literal: "{[1,30)}"
DETAIL: Missing left parenthesis or bracket.
HINT: Element "range_bounds_histogram" in expression -2 could not be parsed.
pg_restore_extended_stats
---------------------------
f
(1 row)
-- ok: range stats for domains over range and multirange types
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'statistics_schemaname', 'stats_import',
'statistics_name', 'test_dom_stat',
'inherited', false,
'exprs', '[{"range_length_histogram": "{2,4,4}",
"range_empty_frac": "0",
"range_bounds_histogram": "{\"[1,3)\",\"[5,9)\",\"[11,15)\"}"},
{"range_length_histogram": "{29,29,109}",
"range_empty_frac": "0",
"range_bounds_histogram": "{\"[1,30)\",\"[11,30)\",\"[21,130)\"}"}]'::jsonb);
pg_restore_extended_stats
---------------------------
t
(1 row)
SELECT e.expr, e.range_length_histogram, e.range_empty_frac,
e.range_bounds_histogram
FROM pg_stats_ext_exprs AS e
WHERE e.statistics_schemaname = 'stats_import' AND
e.statistics_name = 'test_dom_stat' AND
e.inherited = false
\gx
-[ RECORD 1 ]----------+--------------------------------------------------------------------------------
expr | (range_merge((drange)::int4range, (drange)::int4range))::stats_import.dom_range
range_length_histogram | {2,4,4}
range_empty_frac | 0
range_bounds_histogram | {"[1,3)","[5,9)","[11,15)"}
-[ RECORD 2 ]----------+--------------------------------------------------------------------------------
expr | (((dmrange)::int4multirange + '{}'::int4multirange))::stats_import.dom_mrange
range_length_histogram | {29,29,109}
range_empty_frac | 0
range_bounds_histogram | {"[1,30)","[11,30)","[21,130)"}
-- Check import of MCELEM stats for an expression whose type is a domain
-- over tsvector.
CREATE STATISTICS stats_import.test_dom_ts_stat
ON id, (strip(v)::stats_import.dom_tsvector)
FROM stats_import.test_dom_ts;
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
'relname', 'test_dom_ts',
'statistics_schemaname', 'stats_import',
'statistics_name', 'test_dom_ts_stat',
'inherited', false,
'exprs', '[{"most_common_elems": "{brown,fox,quick}",
"most_common_elem_freqs": "{0.3,0.2,0.2,0.3,0.0}",
"elem_count_histogram": "{4,4,4,4,4,4,4,4,4,4}"}]'::jsonb);
pg_restore_extended_stats
---------------------------
t
(1 row)
SELECT e.expr, e.most_common_elems, e.most_common_elem_freqs,
e.elem_count_histogram
FROM pg_stats_ext_exprs AS e
WHERE e.statistics_schemaname = 'stats_import' AND
e.statistics_name = 'test_dom_ts_stat' AND
e.inherited = false
\gx
-[ RECORD 1 ]----------+--------------------------------------------------
expr | (strip((v)::tsvector))::stats_import.dom_tsvector
most_common_elems | {brown,fox,quick}
most_common_elem_freqs | {0.3,0.2,0.2,0.3,0}
elem_count_histogram | {4,4,4,4,4,4,4,4,4,4}
-- Incorrect extended stats kind, exprs not supported
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
@@ -3635,7 +3957,7 @@ SELECT COUNT(*) FROM stats_import.test_range_expr_null
(1 row)
DROP SCHEMA stats_import CASCADE;
NOTICE: drop cascades to 19 other objects
NOTICE: drop cascades to 27 other objects
DETAIL: drop cascades to view stats_import.pg_stats_stable
drop cascades to view stats_import.pg_statistic_flat_t
drop cascades to function stats_import.pg_statistic_flat(text)
@@ -3652,6 +3974,14 @@ drop cascades to table stats_import.test_mr
drop cascades to table stats_import.part_parent
drop cascades to sequence stats_import.testseq
drop cascades to view stats_import.testview
drop cascades to type stats_import.dom_int4
drop cascades to type stats_import.dom_range
drop cascades to type stats_import.dom_mrange
drop cascades to table stats_import.test_dom
drop cascades to table stats_import.test_dom_clone
drop cascades to type stats_import.dom_tsvector
drop cascades to table stats_import.test_dom_ts
drop cascades to table stats_import.test_dom_ts_clone
drop cascades to table stats_import.test_clone
drop cascades to table stats_import.test_mr_clone
drop cascades to table stats_import.test_range_expr_null

View File

@@ -1064,6 +1064,188 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'range_bounds_histogram', '{"[1,30)","[11,30)","[21,130)"}'::text
);
-- test for domains over range and multirange types
CREATE DOMAIN stats_import.dom_int4 AS int4;
CREATE DOMAIN stats_import.dom_range AS int4range;
CREATE DOMAIN stats_import.dom_mrange AS int4multirange;
CREATE TABLE stats_import.test_dom(
id stats_import.dom_int4,
drange stats_import.dom_range,
dmrange stats_import.dom_mrange
) WITH (autovacuum_enabled = false);
INSERT INTO stats_import.test_dom
VALUES (1, '[1,3)', '{[1,3),[5,9),[20,30)}'),
(2, '[5,9)', '{[11,13),[15,19),[20,30)}'),
(3, '[11,15)', '{[21,23),[25,29),[120,130)}');
-- warn: domain over a scalar type cannot have range stats
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'attname', 'id',
'inherited', false,
'null_frac', 0.25::real,
'range_length_histogram', '{2,4,4}'::text,
'range_empty_frac', '0'::real,
'range_bounds_histogram', '{"[1,3)","[5,9)","[11,15)"}'::text
);
SELECT *
FROM stats_import.pg_stats_stable
WHERE schemaname = 'stats_import'
AND tablename = 'test_dom'
AND inherited = false
AND attname = 'id';
-- ok: range stats for a domain over a range type
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'attname', 'drange',
'inherited', false,
'range_length_histogram', '{2,4,4}'::text,
'range_empty_frac', '0'::real,
'range_bounds_histogram', '{"[1,3)","[5,9)","[11,15)"}'::text
);
SELECT *
FROM stats_import.pg_stats_stable
WHERE schemaname = 'stats_import'
AND tablename = 'test_dom'
AND inherited = false
AND attname = 'drange';
-- ok: range stats for a domain over a multirange type.
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'attname', 'dmrange',
'inherited', false,
'range_length_histogram', '{29,29,109}'::text,
'range_empty_frac', '0'::real,
'range_bounds_histogram', '{"[1,30)","[11,30)","[21,130)"}'::text
);
SELECT *
FROM stats_import.pg_stats_stable
WHERE schemaname = 'stats_import'
AND tablename = 'test_dom'
AND inherited = false
AND attname = 'dmrange';
-- warn: multirange values in the bounds histogram of a domain. These
-- must be ranges.
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'attname', 'dmrange',
'inherited', false,
'range_length_histogram', '{29,29,109}'::text,
'range_empty_frac', '0'::real,
'range_bounds_histogram', '{"{[1,30)}","{[11,30)}"}'::text
);
--
-- Check that the range stats that ANALYZE generates for domains over range
-- and multirange types can be restored exactly.
--
ANALYZE stats_import.test_dom;
CREATE TABLE stats_import.test_dom_clone ( LIKE stats_import.test_dom )
WITH (autovacuum_enabled = false);
SELECT 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', 'test_dom_clone',
'attname', s.attname::text,
'inherited', s.inherited,
'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,
'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 = 'test_dom'
ORDER BY s.attname, s.inherited;
SELECT relname, (stats).*
FROM stats_import.pg_statistic_get_difference('test_dom', 'test_dom_clone')
\gx
-- test for a domain over tsvector.
CREATE DOMAIN stats_import.dom_tsvector AS tsvector;
CREATE TABLE stats_import.test_dom_ts(
id int,
v stats_import.dom_tsvector
) WITH (autovacuum_enabled = false);
INSERT INTO stats_import.test_dom_ts
SELECT g, to_tsvector('english', 'the quick brown fox ' || g)
FROM generate_series(1, 20) AS g;
-- ok: mcelem and elem_count_histogram for a domain over tsvector
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'relname', 'test_dom_ts',
'attname', 'v',
'inherited', false,
'most_common_elems', '{brown,fox,quick}'::text,
'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
'elem_count_histogram', '{4,4,4,4,4,4,4,4,4,4}'::real[]);
SELECT *
FROM stats_import.pg_stats_stable
WHERE schemaname = 'stats_import'
AND tablename = 'test_dom_ts'
AND inherited = false
AND attname = 'v';
--
-- Check that the statistics that ANALYZE generates for a domain over
-- tsvector can be restored exactly.
--
ANALYZE stats_import.test_dom_ts;
CREATE TABLE stats_import.test_dom_ts_clone ( LIKE stats_import.test_dom_ts )
WITH (autovacuum_enabled = false);
SELECT 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', 'test_dom_ts_clone',
'attname', s.attname::text,
'inherited', s.inherited,
'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) AS r
WHERE s.schemaname = 'stats_import'
AND s.tablename = 'test_dom_ts'
ORDER BY s.attname, s.inherited;
SELECT relname, (stats).*
FROM stats_import.pg_statistic_get_difference('test_dom_ts', 'test_dom_ts_clone')
\gx
--
-- 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
@@ -1804,6 +1986,75 @@ WHERE e.statistics_schemaname = 'stats_import' AND
e.inherited = false
\gx
-- Check import of range stats for expressions whose type is a domain over a
-- range or a multirange type.
CREATE STATISTICS stats_import.test_dom_stat
ON id,
(range_merge(drange, drange)::stats_import.dom_range),
((dmrange + '{}'::int4multirange)::stats_import.dom_mrange)
FROM stats_import.test_dom;
-- warn: reject multirange values in the bounds histogram of a domain. These
-- must be ranges.
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'statistics_schemaname', 'stats_import',
'statistics_name', 'test_dom_stat',
'inherited', false,
'exprs', '[{"range_length_histogram": "{2,4,4}",
"range_empty_frac": "0",
"range_bounds_histogram": "{\"[1,3)\",\"[5,9)\",\"[11,15)\"}"},
{"range_length_histogram": "{29,29,109}",
"range_empty_frac": "0",
"range_bounds_histogram": "{\"{[1,30)}\",\"{[11,30)}\"}"}]'::jsonb);
-- ok: range stats for domains over range and multirange types
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
'relname', 'test_dom',
'statistics_schemaname', 'stats_import',
'statistics_name', 'test_dom_stat',
'inherited', false,
'exprs', '[{"range_length_histogram": "{2,4,4}",
"range_empty_frac": "0",
"range_bounds_histogram": "{\"[1,3)\",\"[5,9)\",\"[11,15)\"}"},
{"range_length_histogram": "{29,29,109}",
"range_empty_frac": "0",
"range_bounds_histogram": "{\"[1,30)\",\"[11,30)\",\"[21,130)\"}"}]'::jsonb);
SELECT e.expr, e.range_length_histogram, e.range_empty_frac,
e.range_bounds_histogram
FROM pg_stats_ext_exprs AS e
WHERE e.statistics_schemaname = 'stats_import' AND
e.statistics_name = 'test_dom_stat' AND
e.inherited = false
\gx
-- Check import of MCELEM stats for an expression whose type is a domain
-- over tsvector.
CREATE STATISTICS stats_import.test_dom_ts_stat
ON id, (strip(v)::stats_import.dom_tsvector)
FROM stats_import.test_dom_ts;
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
'relname', 'test_dom_ts',
'statistics_schemaname', 'stats_import',
'statistics_name', 'test_dom_ts_stat',
'inherited', false,
'exprs', '[{"most_common_elems": "{brown,fox,quick}",
"most_common_elem_freqs": "{0.3,0.2,0.2,0.3,0.0}",
"elem_count_histogram": "{4,4,4,4,4,4,4,4,4,4}"}]'::jsonb);
SELECT e.expr, e.most_common_elems, e.most_common_elem_freqs,
e.elem_count_histogram
FROM pg_stats_ext_exprs AS e
WHERE e.statistics_schemaname = 'stats_import' AND
e.statistics_name = 'test_dom_ts_stat' AND
e.inherited = false
\gx
-- Incorrect extended stats kind, exprs not supported
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',