Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x9ayV-000000027Ls-1sHE for pgsql-bugs@arkaria.postgresql.org; Thu, 24 Sep 2026 04:22:24 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.98.2) (envelope-from ) id 1x9ayU-00000009HaB-05g3 for pgsql-bugs@arkaria.postgresql.org; Thu, 24 Sep 2026 04:22:22 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x9ayT-00000009Ha3-04U6 for pgsql-bugs@lists.postgresql.org; Thu, 24 Sep 2026 04:22:21 +0000 Received: from fhigh-b1-smtp.messagingengine.com ([202.12.124.152]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x9ayP-00000000y2c-0bvN for pgsql-bugs@lists.postgresql.org; Thu, 24 Sep 2026 04:22:20 +0000 Received: from phl-compute-01.internal (phl-compute-01.internal [10.202.2.41]) by mailfhigh.stl.internal (Postfix) with ESMTP id E81D97A00CD; Thu, 24 Sep 2026 00:22:15 -0400 (EDT) Received: from phl-frontend-03 ([10.202.2.162]) by phl-compute-01.internal (MEProxy); Thu, 24 Sep 2026 00:22:16 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h= cc:cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm1; t=1790223735; x=1790310135; bh=zuiJBXONty LcCZ5Yjqhj3il/SJllsBqCLo6cILewE8Y=; b=SdP5MVnFf9EHISK0ZBlfuee+iC CFxbPwaaFHoUl6AyRfi9fD99a99tpC/tZd+RXPoe94SdOdwoQabBVc6tUCdhfupP RUpz1TX1wm1vqj95RLNGqJKtOT9rUowEFwQRfIXNrg8KnnJ2R2HoYyFABKuel80e VF+0FNgVzz0gdnyWYUCLWT3jGKcUv0KHxgKtw22tqRJruXyon/SoFhXWZboB+WXA Oj/6F+ksWPl9iPYJ/olokpaW6/P7JJE21t/vhChi1DBJOjOuT0OC/v62hcwHquc+ POBQktJ07lRzTodzXCWgvOmEeGALqAEtQjEkgj3Acm6vjPgL20Gbsr96IXnQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm1; t= 1790223735; x=1790310135; bh=zuiJBXONtyLcCZ5Yjqhj3il/SJllsBqCLo6 cILewE8Y=; b=TQS1wWOUfTWdNHONOJk4XnbNDRaqrWCmOasewobIwg5EoLY59bu El2MlgIp+SgOAJiHbxbt2obkJ6woT7pltAaf/Mf04oZIDhZTxugl4en2pUu8v1+W EPfIzo8g4Tywv+6PG3dUEXQ3yBe6otFsDeAYuxHKuhAiaPNhiAzkMGqGoCNINtWf PkJTpZAsekI/GcxDCKbqHwszSjYWrT8nwd7svJEt0nZ9kXwZMckyEPV93oZr9V1u Ce7w9UuKaY4FvpuWUDKE/B92NdJ4w3VYUtLMpd+H+pBITfiAJPgOo5A3Z2aWL3L4 h2KDleH06tplPkL9pYpR0JWMmnbPoMjTeIg== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: dmFkZTE4TWd3Wr1cVh8+LVAYIt9Fk7GcrSy1MrueOewazGh4L93o32CPPWNuv2c3M0Ny3x 65ofnGLdODh4LqSBIFo58yGXmCeQ/hRbF9tWJaz7lSW4FXRgfJ2PrAd4cshOvymvYKuEkC dGz2Z5QtILn99+E2i/Guw/cpT+z04qVeOQczffRpvB5f+YNcZOMyA/TU6hX+ZaWVCLu/bY stYQQLCDwDhDaOIRsc37KdXQx4MgIppVxnQM0ZdgEGaLdlOvSicGdLcTxBmdAnEIt5d18T mluLABccA4kV5x8j2ey7rhr7MsuGpJdrATs5zE46eKm/cRvxI68PO5QPeDUJ6ltW4M5iVo c3tbyiQ1nM8WLh7WbervqYtNSsVqATQpxkGVaybZ/OV/B1F45SvW8B5kyE2nv/zQslWMBm 8lQyTTG97CSa260PyPO/w599YaZJ2KMTqM39szwts7TB7kMMcHmhDzekw8U7nLJK9UUEmo WMik61aAKMu9lpU3d4aN9HQNGtQzkOOULFWf/JESejkRS2EFSuC961pog3np2qT2KZnjlV ZW/95su1FLDnUupI5xCg8itQq0TKRdrqCn5rfs6P26sqVKotKHXKxt3WeBzIrQqkZhEazm tvYBzPObdDKu+WuiU/HZCVrITAt1E/3GkZriYhqVGTy366LsCQJ+P0zXHayw X-ME-Proxy: Feedback-ID: i0fe9450f:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu, 24 Sep 2026 00:22:14 -0400 (EDT) Date: Thu, 24 Sep 2026 13:22:10 +0900 From: Michael Paquier To: Corey Huinker Cc: imchifan@163.com, pgsql-bugs@lists.postgresql.org Subject: Re: BUG #19715: pg_restore_attribute_stats() rejects range statistics for a domain over int4multirange Message-ID: References: <19715-b8be35083016f289@postgresql.org> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="seXSqf4449LT12rx" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --seXSqf4449LT12rx Content-Type: multipart/mixed; boundary="QAD3mgFegsy4fJhQ" Content-Disposition: inline --QAD3mgFegsy4fJhQ Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Wed, Sep 23, 2026 at 03:02:30PM -0400, Corey Huinker wrote: > I'm looking into this. I have begun looking at this before you had sent this reply, and we are handling the base type of a domain in an incorrect way, assuming that for attribute and extended stats we should just always check for TYPTYPE_[MULTI]RANGE, but domains don't map with that at all. I think that we are missing an extra getBaseType(), like [multi]range_typanalyze(), where we use a [multi]range_get_typcache() to cope with domains (getBaseTypeAndTypmod() does the job in the typcache). That's also mentioned in the code. And the same can be said for expressions in extended stats where a domain that has a [multi]range type is involved. We would be better getting rid of these hardcoded TYPTYPE values, IMO. Spoiler: the tests are boring, still required. And fortunately, the only damage is stats data not restored but skipped. Annoying, but not as annoying as in the class of problems labelled like "I corrupt the catalogs". What do you think? -- Michael --QAD3mgFegsy4fJhQ Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=0001-Fix-import-of-statistics-for-domains-over-multi-rang.patch Content-Transfer-Encoding: quoted-printable =46rom 5c2245a2144f5218b4a104043052ae4f76be2bca Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Thu, 24 Sep 2026 13:04:53 +0900 Subject: [PATCH] Fix import of statistics for domains over [multi]range typ= es 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. ANALYZE is able to generate such stats, incorporating the knowledge to handle domains in the in-core typanalyze callbacks. pg_restore_extended_stats() had the same problem for expressions whose type is a domain over a range or a multirange type, assuming that it was safe to rely on a hardcoded TYPTYPE_RANGE or TYPTYPE_MULTIRANGE. This problem is resolved with the introduction of a new routine that englobes the decision of the type to use, statatt_get_range_type(), able to work for domains, range and multirange types. The base type is resolved first. Tests are included in a fashion consistent with the surroundings. The consequence of this issue was the rejection of stats that ANALYZE is able to build, not critical, still annoying. Reported-by: Qifan Liu Discussion: https://postgr.es/m/19715-b8be35083016f289@postgresql.org Backpatch-through: 18 --- src/include/statistics/stat_utils.h | 1 + src/backend/statistics/attribute_stats.c | 12 +- src/backend/statistics/extended_stats_funcs.c | 12 +- src/backend/statistics/stat_utils.c | 30 +++ src/test/regress/expected/stats_import.out | 224 +++++++++++++++++- src/test/regress/sql/stats_import.sql | 163 +++++++++++++ 6 files changed, 424 insertions(+), 18 deletions(-) diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/s= tat_utils.h index 15e962dbb7c8..8ade6b140aa9 100644 --- a/src/include/statistics/stat_utils.h +++ b/src/include/statistics/stat_utils.h @@ -57,6 +57,7 @@ extern Datum statatt_build_stavalues(const char *staname,= FmgrInfo *array_in, Da Oid typid, int32 typmod, bool *ok); extern bool statatt_get_elem_type(Oid atttypid, char atttyptype, Oid *elemtypid, Oid *elem_eq_opr); +extern bool statatt_get_range_type(Oid atttypid, Oid *rangetypid); =20 extern bool statatt_check_bounds_histogram(Datum arrayval); =20 diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statist= ics/attribute_stats.c index c35892ce6d0b..d5596d50f802 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -229,6 +229,8 @@ attribute_statistics_update_internal(Oid reloid, Oid elemtypid =3D InvalidOid; Oid elem_eq_opr =3D InvalidOid; =20 + Oid bounds_typid =3D InvalidOid; + FmgrInfo array_in_fn; =20 bool do_mcv =3D !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) && @@ -334,7 +336,7 @@ attribute_statistics_update_internal(Oid reloid, =20 /* only range types can have range stats */ if ((do_range_length_histogram || do_bounds_histogram) && - !(atttyptype =3D=3D TYPTYPE_RANGE || atttyptype =3D=3D TYPTYPE_MULTIRANG= E)) + !statatt_get_range_type(atttypid, &bounds_typid)) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -498,14 +500,8 @@ attribute_statistics_update_internal(Oid reloid, { bool converted =3D false; Datum stavalues; - Oid bounds_typid =3D atttypid; =20 - /* - * If it's a multirange, step down to the range type, as is done by - * multirange_typanalyze(). - */ - if (type_is_multirange(atttypid)) - bounds_typid =3D get_multirange_range(atttypid); + Assert(OidIsValid(bounds_typid)); =20 stavalues =3D statatt_build_stavalues("range_bounds_histogram", &array_in_fn, diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/st= atistics/extended_stats_funcs.c index cb965fdb8680..37013883d465 100644 --- a/src/backend/statistics/extended_stats_funcs.c +++ b/src/backend/statistics/extended_stats_funcs.c @@ -1123,6 +1123,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *co= nt, Datum pgstdat =3D (Datum) 0; Oid elemtypid =3D InvalidOid; Oid elemeqopr =3D InvalidOid; + Oid rtypid =3D InvalidOid; bool found[NUM_ATTRIBUTE_STATS_ELEMS] =3D {0}; JsonbValue val[NUM_ATTRIBUTE_STATS_ELEMS] =3D {0}; =20 @@ -1259,8 +1260,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *co= nt, found[RANGE_EMPTY_FRAC_ELEM] || found[RANGE_BOUNDS_HISTOGRAM_ELEM]) { - if (typcache->typtype !=3D TYPTYPE_RANGE && - typcache->typtype !=3D TYPTYPE_MULTIRANGE) + if (!statatt_get_range_type(typid, &rtypid)) { ereport(WARNING, errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -1476,14 +1476,8 @@ import_pg_statistic(Relation pgsd, JsonbContainer *c= ont, Datum stavalues; bool val_ok =3D false; char *s; - Oid rtypid =3D typid; =20 - /* - * If it's a multirange, step down to the range type, as is done by - * multirange_typanalyze(). - */ - if (type_is_multirange(typid)) - rtypid =3D get_multirange_range(typid); + Assert(OidIsValid(rtypid)); =20 s =3D jbv_string_get_cstr(&val[RANGE_BOUNDS_HISTOGRAM_ELEM]); =20 diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/s= tat_utils.c index f4ff9ab9b20c..5e0e8b9b3256 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -551,6 +551,36 @@ statatt_get_elem_type(Oid atttypid, char atttyptype, return true; } =20 +/* + * Derive the range type to use from the attribute type, returning false if + * the attribute cannot have range statistics at all. + * + * The attribute type may be a domain, in which case its base type decides + * whether range statistics apply (see also range_typanalyze() and + * multirange_typanalyze()). For a multirange type, we step down to its + * range type, because compute_range_stats() stores range bounds even when + * analyzing a multirange column. + * + * The atttypid should be derived from a previous call to statatt_get_type= (). + */ +bool +statatt_get_range_type(Oid atttypid, Oid *rangetypid) +{ + Oid basetypid =3D getBaseType(atttypid); + + if (type_is_multirange(basetypid)) + *rangetypid =3D get_multirange_range(basetypid); + else if (type_is_range(basetypid)) + *rangetypid =3D basetypid; + else + { + *rangetypid =3D 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/test/regress/expected/stats_import.out b/src/test/regress/= expected/stats_import.out index 4ce176c26678..e1de8d00d5b4 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -1495,6 +1495,158 @@ SELECT pg_catalog.pg_restore_attribute_stats( t (1 row) =20 +-- 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 =3D 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 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_KIN= D_BOUNDS_HISTOGRAM. + pg_restore_attribute_stats=20 +---------------------------- + f +(1 row) + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname =3D 'stats_import' +AND tablename =3D 'test_dom' +AND inherited =3D false +AND attname =3D 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | = n_distinct | most_common_vals | most_common_freqs | histogram_bounds | corr= elation | most_common_elems | most_common_elem_freqs | elem_count_histogram= | range_length_histogram | range_empty_frac | range_bounds_histogram=20 +--------------+-----------+---------+-----------+-----------+-----------+-= -----------+------------------+-------------------+------------------+-----= --------+-------------------+------------------------+---------------------= -+------------------------+------------------+------------------------ + stats_import | test_dom | id | f | 0.25 | 0 | = 0 | | | | = | | | = | | |=20 +(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=20 +---------------------------- + t +(1 row) + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname =3D 'stats_import' +AND tablename =3D 'test_dom' +AND inherited =3D false +AND attname =3D 'drange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | = n_distinct | most_common_vals | most_common_freqs | histogram_bounds | corr= elation | most_common_elems | most_common_elem_freqs | elem_count_histogram= | range_length_histogram | range_empty_frac | range_bounds_histogram = =20 +--------------+-----------+---------+-----------+-----------+-----------+-= -----------+------------------+-------------------+------------------+-----= --------+-------------------+------------------------+---------------------= -+------------------------+------------------+----------------------------- + 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=20 +---------------------------- + t +(1 row) + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname =3D 'stats_import' +AND tablename =3D 'test_dom' +AND inherited =3D false +AND attname =3D 'dmrange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | = n_distinct | most_common_vals | most_common_freqs | histogram_bounds | corr= elation | most_common_elems | most_common_elem_freqs | elem_count_histogram= | range_length_histogram | range_empty_frac | range_bounds_histogram = =20 +--------------+-----------+---------+-----------+-----------+-----------+-= -----------+------------------+-------------------+------------------+-----= --------+-------------------+------------------------+---------------------= -+------------------------+------------------+-----------------------------= ---- + 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=20 +---------------------------- + 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 =3D 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 =3D 'stats_import' +AND s.tablename =3D 'test_dom' +ORDER BY s.attname, s.inherited; + attname | inherited | r=20 +---------+-----------+--- + 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 the ability to exactly copy data from one table to an identical ta= ble, -- correctly reconstructing the stakind order as well as the staopN and @@ -2737,6 +2889,71 @@ range_length_histogram | {10179,10189,10199} range_empty_frac | 0 range_bounds_histogram | {"[1,10200)","[11,10200)","[21,10200)"} =20 +-- 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. Th= ese +-- 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 pars= ed. + pg_restore_extended_stats=20 +--------------------------- + 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=20 +--------------------------- + 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 =3D 'stats_import' AND + e.statistics_name =3D 'test_dom_stat' AND + e.inherited =3D false +\gx +-[ RECORD 1 ]----------+--------------------------------------------------= ------------------------------ +expr | (range_merge((drange)::int4range, (drange)::int4r= ange))::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 + '{}'::int4multirang= e))::stats_import.dom_mrange +range_length_histogram | {29,29,109} +range_empty_frac | 0 +range_bounds_histogram | {"[1,30)","[11,30)","[21,130)"} + -- Incorrect extended stats kind, exprs not supported SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', @@ -3828,7 +4045,7 @@ SELECT COUNT(*) FROM stats_import.test_range_expr_null (1 row) =20 DROP SCHEMA stats_import CASCADE; -NOTICE: drop cascades to 19 other objects +NOTICE: drop cascades to 24 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) @@ -3845,6 +4062,11 @@ 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 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/s= tats_import.sql index 748c9a2e0000..417bcb025339 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -1082,6 +1082,124 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'range_bounds_histogram', '{"[1,30)","[11,30)","[21,130)"}'::text ); =20 +-- 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 =3D 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 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 =3D 'stats_import' +AND tablename =3D 'test_dom' +AND inherited =3D false +AND attname =3D '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 =3D 'stats_import' +AND tablename =3D 'test_dom' +AND inherited =3D false +AND attname =3D '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 =3D 'stats_import' +AND tablename =3D 'test_dom' +AND inherited =3D false +AND attname =3D '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 =3D 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 =3D 'stats_import' +AND s.tablename =3D '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 the ability to exactly copy data from one table to an identical ta= ble, -- correctly reconstructing the stakind order as well as the staopN and @@ -1934,6 +2052,51 @@ WHERE e.statistics_schemaname =3D 'stats_import' AND e.inherited =3D false \gx =20 +-- 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. Th= ese +-- 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 =3D 'stats_import' AND + e.statistics_name =3D 'test_dom_stat' AND + e.inherited =3D false +\gx + -- Incorrect extended stats kind, exprs not supported SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', --=20 2.55.0 --QAD3mgFegsy4fJhQ-- --seXSqf4449LT12rx Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmq0pXIACgkQnvQgOdby QH2HFg//W99ckxO26dEEmeh3WN5xIVM5ie/ZnGrs3aXa8w5BhlwfV/Vpo3uc0pHW C3QW9sWs5dOWlrlCIULKkYdXqSHhlNRW2zExOYgJpR5pFNNpbQ6uIEfqEMlWGhv5 40gv5u7JqAaY8lyNHlFsn6ORrwCL6RzQUS6AKfoX6mcbyvSUvaitYYctdG/czcsa b6duevw/69dKz+0UdjzZdt/dkE347pm99vJq33/zJTouWSELacyoFgt0A0C266We PzBnT47lXpUEajzpMNH751kydS8SCtkflsH1O8aqFo7smCMwcM9Ja2sD4843Blsf iLzyxTuzx8p+hNc3h7MIogjvZELH2JuQTjVaUAWWsqkaKN6TWtKPNsnMWvYKN3dN ap602xv5B8TE8gG8ZQZ01YvkNFpXIlGmy+escwgDj1QQC+mrZOY4hgv23tF062t2 BmJXqOfRZR3BW/uoUzukg55gl3ENyQCDpsLB+Fsqi935tY1769b6vm6TOsxjyqs2 THvS6LGCKjQD2EWPBaucxnhgoNIDJbmdqCUlB8Amfq63bLAxnGQEHlyN5YmptuHP UQaJ5+VNuDh+/CntyO9/Bc5Co2lxJzRHrrNNw/bPqZLkz4fNaNQsauZMHKx2C13O wDKrwJwXpqtBE5azL3zg4drqsa7TScKcKfCMiOKfUcL6auFCfN0= =syvK -----END PGP SIGNATURE----- --seXSqf4449LT12rx--