Skip to content

Commit de3d2df

Browse files
committed
Fix CheckAttributeType's handling of collations for ranges.
Commit fc76958 changed CheckAttributeType to recurse into ranges, but made it pass down the wrong collation (always InvalidOid, since ranges as such have no collation). This would result in guaranteed failure when considering a range type whose subtype is collatable. Embarrassingly, we lack any regression tests that would expose such a problem (but fortunately, somebody noticed before we shipped this bug in any release). Fix it to pass down the range's subtype collation property instead, and add some regression test cases to exercise collatable-subtype ranges a bit more. Back-patch to all supported branches, as the previous patch was. Report and patch by Julien Rouhaud, test cases tweaked by me Discussion: https://postgr.es/m/CAOBaU_aBWqNweiGUFX0guzBKkcfJ8mnnyyGC_KBQmO12Mj5f_A@mail.gmail.com
1 parent 8b1d447 commit de3d2df

File tree

6 files changed

+148
-3
lines changed

6 files changed

+148
-3
lines changed

src/backend/catalog/heap.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,8 @@ CheckAttributeType(const char *attname,
564564
/*
565565
* If it's a range, recurse to check its subtype.
566566
*/
567-
CheckAttributeType(attname, get_range_subtype(atttypid), attcollation,
567+
CheckAttributeType(attname, get_range_subtype(atttypid),
568+
get_range_collation(atttypid),
568569
containing_rowtypes,
569570
allow_system_table_mods);
570571
}

src/backend/utils/cache/lsyscache.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3112,3 +3112,29 @@ get_range_subtype(Oid rangeOid)
31123112
else
31133113
return InvalidOid;
31143114
}
3115+
3116+
/*
3117+
* get_range_collation
3118+
* Returns the collation of a given range type
3119+
*
3120+
* Returns InvalidOid if the type is not a range type,
3121+
* or if its subtype is not collatable.
3122+
*/
3123+
Oid
3124+
get_range_collation(Oid rangeOid)
3125+
{
3126+
HeapTuple tp;
3127+
3128+
tp = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rangeOid));
3129+
if (HeapTupleIsValid(tp))
3130+
{
3131+
Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp);
3132+
Oid result;
3133+
3134+
result = rngtup->rngcollation;
3135+
ReleaseSysCache(tp);
3136+
return result;
3137+
}
3138+
else
3139+
return InvalidOid;
3140+
}

src/include/utils/lsyscache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ extern void free_attstatsslot(AttStatsSlot *sslot);
177177
extern char *get_namespace_name(Oid nspid);
178178
extern char *get_namespace_name_or_temp(Oid nspid);
179179
extern Oid get_range_subtype(Oid rangeOid);
180+
extern Oid get_range_collation(Oid rangeOid);
180181

181182
#define type_is_array(typid) (get_element_type(typid) != InvalidOid)
182183
/* type_is_array_domain accepts both plain arrays and domains over arrays */

src/test/regress/expected/rangetypes.out

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,95 @@ select * from numrange_test natural join numrange_test2 order by nr;
582582
set enable_nestloop to default;
583583
set enable_hashjoin to default;
584584
set enable_mergejoin to default;
585-
DROP TABLE numrange_test;
585+
-- keep numrange_test around to help exercise dump/reload
586586
DROP TABLE numrange_test2;
587+
--
588+
-- Apply a subset of the above tests on a collatable type, too
589+
--
590+
CREATE TABLE textrange_test (tr textrange);
591+
create index textrange_test_btree on textrange_test(tr);
592+
INSERT INTO textrange_test VALUES('[,)');
593+
INSERT INTO textrange_test VALUES('["a",]');
594+
INSERT INTO textrange_test VALUES('[,"q")');
595+
INSERT INTO textrange_test VALUES(textrange('b', 'g'));
596+
INSERT INTO textrange_test VALUES('empty');
597+
INSERT INTO textrange_test VALUES(textrange('d', 'd', '[]'));
598+
SELECT tr, isempty(tr), lower(tr), upper(tr) FROM textrange_test;
599+
tr | isempty | lower | upper
600+
-------+---------+-------+-------
601+
(,) | f | |
602+
[a,) | f | a |
603+
(,q) | f | | q
604+
[b,g) | f | b | g
605+
empty | t | |
606+
[d,d] | f | d | d
607+
(6 rows)
608+
609+
SELECT tr, lower_inc(tr), lower_inf(tr), upper_inc(tr), upper_inf(tr) FROM textrange_test;
610+
tr | lower_inc | lower_inf | upper_inc | upper_inf
611+
-------+-----------+-----------+-----------+-----------
612+
(,) | f | t | f | t
613+
[a,) | t | f | f | t
614+
(,q) | f | t | f | f
615+
[b,g) | t | f | f | f
616+
empty | f | f | f | f
617+
[d,d] | t | f | t | f
618+
(6 rows)
619+
620+
SELECT * FROM textrange_test WHERE range_contains(tr, textrange('f', 'fx'));
621+
tr
622+
-------
623+
(,)
624+
[a,)
625+
(,q)
626+
[b,g)
627+
(4 rows)
628+
629+
SELECT * FROM textrange_test WHERE tr @> textrange('a', 'z');
630+
tr
631+
------
632+
(,)
633+
[a,)
634+
(2 rows)
635+
636+
SELECT * FROM textrange_test WHERE range_contained_by(textrange('0','9'), tr);
637+
tr
638+
------
639+
(,)
640+
(,q)
641+
(2 rows)
642+
643+
SELECT * FROM textrange_test WHERE 'e'::text <@ tr;
644+
tr
645+
-------
646+
(,)
647+
[a,)
648+
(,q)
649+
[b,g)
650+
(4 rows)
651+
652+
select * from textrange_test where tr = 'empty';
653+
tr
654+
-------
655+
empty
656+
(1 row)
657+
658+
select * from textrange_test where tr = '("b","g")';
659+
tr
660+
----
661+
(0 rows)
662+
663+
select * from textrange_test where tr = '["b","g")';
664+
tr
665+
-------
666+
[b,g)
667+
(1 row)
668+
669+
select * from textrange_test where tr < 'empty';
670+
tr
671+
----
672+
(0 rows)
673+
587674
-- test canonical form for int4range
588675
select int4range(1, 10, '[]');
589676
int4range

src/test/regress/expected/sanity_check.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ num_exp_sqrt|t
8888
num_exp_sub|t
8989
num_input_test|f
9090
num_result|f
91+
numrange_test|t
9192
onek|t
9293
onek2|t
9394
path_tbl|f
@@ -184,6 +185,7 @@ test_range_spgist|t
184185
test_tsvector|f
185186
testjsonb|f
186187
text_tbl|f
188+
textrange_test|t
187189
time_tbl|f
188190
timestamp_tbl|f
189191
timestamptz_tbl|f

src/test/regress/sql/rangetypes.sql

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,37 @@ set enable_nestloop to default;
148148
set enable_hashjoin to default;
149149
set enable_mergejoin to default;
150150

151-
DROP TABLE numrange_test;
151+
-- keep numrange_test around to help exercise dump/reload
152152
DROP TABLE numrange_test2;
153153

154+
--
155+
-- Apply a subset of the above tests on a collatable type, too
156+
--
157+
158+
CREATE TABLE textrange_test (tr textrange);
159+
create index textrange_test_btree on textrange_test(tr);
160+
161+
INSERT INTO textrange_test VALUES('[,)');
162+
INSERT INTO textrange_test VALUES('["a",]');
163+
INSERT INTO textrange_test VALUES('[,"q")');
164+
INSERT INTO textrange_test VALUES(textrange('b', 'g'));
165+
INSERT INTO textrange_test VALUES('empty');
166+
INSERT INTO textrange_test VALUES(textrange('d', 'd', '[]'));
167+
168+
SELECT tr, isempty(tr), lower(tr), upper(tr) FROM textrange_test;
169+
SELECT tr, lower_inc(tr), lower_inf(tr), upper_inc(tr), upper_inf(tr) FROM textrange_test;
170+
171+
SELECT * FROM textrange_test WHERE range_contains(tr, textrange('f', 'fx'));
172+
SELECT * FROM textrange_test WHERE tr @> textrange('a', 'z');
173+
SELECT * FROM textrange_test WHERE range_contained_by(textrange('0','9'), tr);
174+
SELECT * FROM textrange_test WHERE 'e'::text <@ tr;
175+
176+
select * from textrange_test where tr = 'empty';
177+
select * from textrange_test where tr = '("b","g")';
178+
select * from textrange_test where tr = '["b","g")';
179+
select * from textrange_test where tr < 'empty';
180+
181+
154182
-- test canonical form for int4range
155183
select int4range(1, 10, '[]');
156184
select int4range(1, 10, '[)');

0 commit comments

Comments
 (0)