diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index 56293d94104..a99bcfedc38 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -104,10 +104,12 @@ static bool attribute_statistics_update(FunctionCallInfo fcinfo); static Node *get_attr_expr(Relation rel, int attnum); static void get_attr_stat_type(Oid reloid, AttrNumber attnum, Oid *atttypid, int32 *atttypmod, - char *atttyptype, Oid *atttypcoll, + TypeCacheEntry **basetypcache, Oid *atttypcoll, Oid *eq_opr, Oid *lt_opr); -static bool get_elem_stat_type(Oid atttypid, char atttyptype, +static bool get_elem_stat_type(TypeCacheEntry *basetypcache, Oid *elemtypid, Oid *elem_eq_opr); +static bool get_range_stat_type(TypeCacheEntry *basetypcache, + Oid *rangetypid); static Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid, int32 typmod, bool *ok); static void set_stats_slot(Datum *values, bool *nulls, bool *replaces, @@ -152,7 +154,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo) Oid atttypid = InvalidOid; int32 atttypmod; - char atttyptype; + TypeCacheEntry *basetypcache; Oid atttypcoll = InvalidOid; Oid eq_opr = InvalidOid; Oid lt_opr = InvalidOid; @@ -160,6 +162,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo) 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) && @@ -290,14 +294,13 @@ attribute_statistics_update(FunctionCallInfo fcinfo) /* derive information from attribute */ get_attr_stat_type(reloid, attnum, &atttypid, &atttypmod, - &atttyptype, &atttypcoll, + &basetypcache, &atttypcoll, &eq_opr, <_opr); /* if needed, derive element type */ if (do_mcelem || do_dechist) { - if (!get_elem_stat_type(atttypid, atttyptype, - &elemtypid, &elem_eq_opr)) + if (!get_elem_stat_type(basetypcache, &elemtypid, &elem_eq_opr)) { ereport(WARNING, (errmsg("could not determine element type of column \"%s\"", attname), @@ -328,7 +331,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo) /* only range types can have range stats */ if ((do_range_length_histogram || do_bounds_histogram) && - !(atttyptype == TYPTYPE_RANGE || atttyptype == TYPTYPE_MULTIRANGE)) + !get_range_stat_type(basetypcache, &bounds_typid)) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -492,14 +495,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo) { 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 = text_to_stavalues("range_bounds_histogram", &array_in_fn, @@ -598,18 +595,20 @@ get_attr_expr(Relation rel, int attnum) /* * Derive type information from the attribute. + * + * *atttypid and *atttypmod describe the type as declared. *basetypcache is + * the cache entry of the base type behind any domain. */ static void get_attr_stat_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)); @@ -656,16 +655,22 @@ get_attr_stat_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); @@ -673,14 +678,17 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum, /* * Derive element type information from the attribute type. + * + * The type cache entry should be derived from a previous call to + * get_attr_stat_type(). */ static bool -get_elem_stat_type(Oid atttypid, char atttyptype, +get_elem_stat_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 @@ -690,8 +698,8 @@ get_elem_stat_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)) @@ -707,6 +715,33 @@ get_elem_stat_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 + * get_attr_stat_type(), so that any domain has already been looked through. + */ +static bool +get_range_stat_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; +} + /* * Cast a text datum into an array with element type elemtypid. * diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index 78e5f4563fb..dc79e7425bc 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -1273,6 +1273,153 @@ 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 pg_stats +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 pg_stats +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 pg_stats +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) + +-- 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 pg_stats +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) + -- -- 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 @@ -1549,11 +1696,17 @@ AND attname = 'i'; DROP TABLE stats_temp; DROP SCHEMA stats_import CASCADE; -NOTICE: drop cascades to 7 other objects +NOTICE: drop cascades to 13 other objects DETAIL: drop cascades to type stats_import.complex_type drop cascades to table stats_import.test 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 type stats_import.dom_tsvector +drop cascades to table stats_import.test_dom_ts drop cascades to table stats_import.test_clone diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 0ba5791e2e5..e68107628b7 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -882,6 +882,119 @@ 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 pg_stats +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 pg_stats +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 pg_stats +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 +); + +-- 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 pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test_dom_ts' +AND inherited = false +AND attname = 'v'; + -- -- 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