diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index a6b118a8d72..1cc4d657231 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -373,10 +373,27 @@ attribute_statistics_update(FunctionCallInfo fcinfo) if (converted) { - statatt_set_slot(values, nulls, replaces, - STATISTIC_KIND_MCV, - eq_opr, atttypcoll, - stanumbers, false, stavalues, false); + ArrayType *vals_arr = DatumGetArrayTypeP(stavalues); + ArrayType *nums_arr = DatumGetArrayTypeP(stanumbers); + int nvals = ARR_DIMS(vals_arr)[0]; + int nnums = ARR_DIMS(nums_arr)[0]; + + if (nvals != nnums) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse \"%s\": incorrect number of elements (same as \"%s\" required)", + "most_common_vals", + "most_common_freqs"))); + result = false; + } + else + { + statatt_set_slot(values, nulls, replaces, + STATISTIC_KIND_MCV, + eq_opr, atttypcoll, + stanumbers, false, stavalues, false); + } } else result = false; diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c index 8537d9e2409..70393d3a904 100644 --- a/src/backend/statistics/extended_stats_funcs.c +++ b/src/backend/statistics/extended_stats_funcs.c @@ -1070,6 +1070,15 @@ array_in_safe(FmgrInfo *array_in, const char *s, Oid typid, int32 typmod, return (Datum) 0; } + if (ARR_NDIM(DatumGetArrayTypeP(result)) != 1) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not import element \"%s\" in expression %d: must be a one-dimensional array", + element_name, exprnum))); + return (Datum) 0; + } + if (array_contains_nulls(DatumGetArrayTypeP(result))) { ereport(WARNING, @@ -1332,10 +1341,27 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont, /* Only set the slot if both datums have been built */ if (val_ok && num_ok) + { + ArrayType *vals_arr = DatumGetArrayTypeP(stavalues); + ArrayType *nums_arr = DatumGetArrayTypeP(stanumbers); + int nvals = ARR_DIMS(vals_arr)[0]; + int nnums = ARR_DIMS(nums_arr)[0]; + + if (nvals != nnums) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse \"%s\": incorrect number of elements (same as \"%s\" required)", + "most_common_vals", + "most_common_freqs"))); + goto pg_statistic_error; + } + statatt_set_slot(values, nulls, replaces, STATISTIC_KIND_MCV, typcache->eq_opr, typcoll, stanumbers, false, stavalues, false); + } else goto pg_statistic_error; } diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index 9c680f1cb37..a673e3c704b 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -600,6 +600,15 @@ statatt_build_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid ty return (Datum) 0; } + if (ARR_NDIM(DatumGetArrayTypeP(result)) != 1) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" must be a one-dimensional array", staname))); + *ok = false; + return (Datum) 0; + } + if (array_contains_nulls(DatumGetArrayTypeP(result))) { ereport(WARNING, diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index fb2b22e7e55..f421e83e232 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -824,6 +824,87 @@ AND attname = 'id'; stats_import | test | id | f | 0.23 | 5 | 0.6 | | | | | | | | | | (1 row) +-- warn: mcv / mcf array length mismatch (more vals), mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.24::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.3,0.25}'::real[] + ); +WARNING: could not parse "most_common_vals": incorrect number of elements (same as "most_common_freqs" required) + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +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 | id | f | 0.24 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: mcv / mcf array length mismatch (more freqs), mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.25::real, + 'most_common_vals', '{2,1}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); +WARNING: could not parse "most_common_vals": incorrect number of elements (same as "most_common_freqs" required) + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +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 | id | f | 0.25 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: most_common_vals is multi-dimensional, mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.26::real, + 'most_common_vals', '{{2,1},{3,4}}'::text, + 'most_common_freqs', '{0.3,0.25,0.05,0.04}'::real[] + ); +WARNING: "most_common_vals" must be a one-dimensional array + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +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 | id | f | 0.26 | 5 | 0.6 | | | | | | | | | | +(1 row) + -- ok: mcv+mcf SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', @@ -846,7 +927,7 @@ 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 | id | f | 0.23 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | | + stats_import | test | id | f | 0.26 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | | (1 row) -- warn: NULL in histogram array, rest get set @@ -2524,6 +2605,23 @@ HINT: "most_common_vals" and "most_common_freqs" must be both either strings or f (1 row) +-- exprs most_common_vals is multi-dimensional +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{{1,2},{3,4}}", "most_common_freqs": "{0.3,0.25,0.05,0.04}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); +WARNING: could not import element "most_common_vals" in expression -1: must be a one-dimensional array + pg_restore_extended_stats +--------------------------- + f +(1 row) + -- exprs most_common_vals element wrong type SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', @@ -2582,6 +2680,23 @@ HINT: Element "most_common_freqs" in expression -1 could not be parsed. f (1 row) +-- exprs most_common_vals / most_common_freqs array length mismatch +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{1,3}", "most_common_freqs": "{0.5}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); +WARNING: could not parse "most_common_vals": incorrect number of elements (same as "most_common_freqs" required) + pg_restore_extended_stats +--------------------------- + f +(1 row) + -- exprs histogram wrong type SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 0bfa3d44cef..c1bf55690a6 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -645,6 +645,60 @@ AND tablename = 'test' AND inherited = false AND attname = 'id'; +-- warn: mcv / mcf array length mismatch (more vals), mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.24::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.3,0.25}'::real[] + ); + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv / mcf array length mismatch (more freqs), mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.25::real, + 'most_common_vals', '{2,1}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: most_common_vals is multi-dimensional, mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.26::real, + 'most_common_vals', '{{2,1},{3,4}}'::text, + 'most_common_freqs', '{0.3,0.25,0.05,0.04}'::real[] + ); + +SELECT * +FROM stats_import.pg_stats_stable +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + -- ok: mcv+mcf SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', @@ -1784,6 +1838,17 @@ SELECT pg_catalog.pg_restore_extended_stats( { "most_common_freqs": "{0.5}" }, { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } ]'::jsonb); +-- exprs most_common_vals is multi-dimensional +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{{1,2},{3,4}}", "most_common_freqs": "{0.3,0.25,0.05,0.04}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); -- exprs most_common_vals element wrong type SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', @@ -1828,6 +1893,17 @@ SELECT pg_catalog.pg_restore_extended_stats( { "most_common_vals": "{1}", "most_common_freqs": "{BADMCF}" }, { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } ]'::jsonb); +-- exprs most_common_vals / most_common_freqs array length mismatch +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{1,3}", "most_common_freqs": "{0.5}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); -- exprs histogram wrong type SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import',