diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index 5649d01ebe7..f7481f887fe 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -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, <_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, diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c index d65a3c395d5..77c2295bb3b 100644 --- a/src/backend/statistics/extended_stats_funcs.c +++ b/src/backend/statistics/extended_stats_funcs.c @@ -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]); diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index b03b33de4f1..a77009d8aaa 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -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. diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h index 74da7790579..8633ffbad9a 100644 --- a/src/include/statistics/stat_utils.h +++ b/src/include/statistics/stat_utils.h @@ -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 */ diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index c484a77f9e0..f9825a12b12 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -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 diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 2eb50adf40b..fd5bf6a3ea2 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -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',