Re: BUG #16122: segfault pg_detoast_datum (datum=0x0) at fmgr.c:1833 numrange query

Andrey Borodin <[email protected]> Sat, 4 Jan 2020 22:57:57 +0500
Newsgroups gmane.comp.db.postgresql.bugs
Message-ID <[email protected]>
--Apple-Mail=_57099874-8E4A-45AA-980A-0B421AE9BE4C
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
	charset=utf-8



> 4 =D1=8F=D0=BD=D0=B2. 2020 =D0=B3., =D0=B2 0:05, Andrey Borodin =
<[email protected]> =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB(=D0=B0):=

>=20
> I believe line should be not like
> +	for (i =3D upper_index - 1; i >=3D 0; i--)
> but rather
> +	for (i =3D min(upper_index, hist_nvalues - 2); i >=3D 0; i--)
>=20
> I will dig into this during this CF. Currently, that's my 2 cents.
I think I have a little more correct fix.

As far as I understand, we have an array of bin lower bounds hist_lower =
with size hist_nvalues.
There is a function get_position(..,value, lower, upper), which =
calculates relative position of value between lower and upper bound.
We call get_position(typcache, lower, &hist_lower[i], &hist_lower[i + =
1]); when i is last bin, i.e. i + 1 =3D=3D hist_nvalues, thus passing =
bogus upper to get_position().
PFA possible fix for this.

Upper in the code same situation when upper is undefined is treated as =
if get_position returned 0.
Also, while get_position() is not prepared to bogus upper, but it is =
well aware of infinite bounds. We could just add last infinite value to =
hist_lower and remove upper_index < hist_nvalues - 1 and i + 1 !=3D =
hist_nvalues checks entirely.

Best regards, Andrey Borodin.

--Apple-Mail=_57099874-8E4A-45AA-980A-0B421AE9BE4C
Content-Disposition: attachment;
	filename=v2-rangetype-stat-crash-from-Andrey.diff
Content-Type: application/octet-stream;
	x-unix-mode=0644;
	name="v2-rangetype-stat-crash-from-Andrey.diff"
Content-Transfer-Encoding: 7bit

diff --git a/src/backend/utils/adt/rangetypes_selfuncs.c b/src/backend/utils/adt/rangetypes_selfuncs.c
index a4d7a7ab42..607385bf44 100644
--- a/src/backend/utils/adt/rangetypes_selfuncs.c
+++ b/src/backend/utils/adt/rangetypes_selfuncs.c
@@ -1063,10 +1063,11 @@ calc_hist_selectivity_contained(TypeCacheEntry *typcache,
 
 			/*
 			 * Subtract from bin_width the portion of this bin that we want to
-			 * ignore.
+			 * ignore, if current bin has finite upper bound.
 			 */
-			bin_width -= get_position(typcache, lower, &hist_lower[i],
-									  &hist_lower[i + 1]);
+			if (i + 1 != hist_nvalues)
+				bin_width -= get_position(typcache, lower, &hist_lower[i],
+										  &hist_lower[i + 1]);
 			if (bin_width < 0.0)
 				bin_width = 0.0;
 			final_bin = true;
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 5ed6ae47ec..8427418bb5 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1434,3 +1434,16 @@ create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
 DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+-- Corner case with selectivity of range operators, per bug #16122.
+CREATE TABLE rangetab (a numrange);
+INSERT INTO rangetab
+SELECT ('['|| (45.0 - a::numeric/10000000) || ',' ||
+              (45.1 + a::numeric/10000000) || ')')::numrange
+  FROM generate_series(1,1000) as a;
+ANALYZE rangetab;
+SELECT a FROM rangetab WHERE a <@ '[89.9999998611111,90.0000001388889)';
+ a 
+---
+(0 rows)
+
+DROP TABLE rangetab;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 2d0ec8964e..d045cb7650 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -510,3 +510,13 @@ create function inoutparam_fail(inout i anyelement, out r anyrange)
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
+
+-- Corner case with selectivity of range operators, per bug #16122.
+CREATE TABLE rangetab (a numrange);
+INSERT INTO rangetab
+SELECT ('['|| (45.0 - a::numeric/10000000) || ',' ||
+              (45.1 + a::numeric/10000000) || ')')::numrange
+  FROM generate_series(1,1000) as a;
+ANALYZE rangetab;
+SELECT a FROM rangetab WHERE a <@ '[89.9999998611111,90.0000001388889)';
+DROP TABLE rangetab;

--Apple-Mail=_57099874-8E4A-45AA-980A-0B421AE9BE4C--