Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Fix CheckAttributeType's handling of collations for ranges.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 31 Jan 2020 22:03:55 +0000 (17:03 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 31 Jan 2020 22:03:55 +0000 (17:03 -0500)
Commit fc7695891 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

src/backend/catalog/heap.c
src/backend/utils/cache/lsyscache.c
src/include/utils/lsyscache.h
src/test/regress/expected/rangetypes.out
src/test/regress/expected/sanity_check.out
src/test/regress/sql/rangetypes.sql

index ad8fec1d80199a4e8ce510fc6233585036bd2693..8ebfe479ebe6a91db896055ad49b1d90ffc3986c 100644 (file)
@@ -578,7 +578,8 @@ CheckAttributeType(const char *attname,
        /*
         * If it's a range, recurse to check its subtype.
         */
-       CheckAttributeType(attname, get_range_subtype(atttypid), attcollation,
+       CheckAttributeType(attname, get_range_subtype(atttypid),
+                          get_range_collation(atttypid),
                           containing_rowtypes,
                           allow_system_table_mods);
    }
index 82d39e9498055ca96f495f1cab90c6b074ce0ea0..a4da9353714287c85d0f46fbf40fa9347c72037e 100644 (file)
@@ -3130,3 +3130,29 @@ get_range_subtype(Oid rangeOid)
    else
        return InvalidOid;
 }
+
+/*
+ * get_range_collation
+ *     Returns the collation of a given range type
+ *
+ * Returns InvalidOid if the type is not a range type,
+ * or if its subtype is not collatable.
+ */
+Oid
+get_range_collation(Oid rangeOid)
+{
+   HeapTuple   tp;
+
+   tp = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rangeOid));
+   if (HeapTupleIsValid(tp))
+   {
+       Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp);
+       Oid         result;
+
+       result = rngtup->rngcollation;
+       ReleaseSysCache(tp);
+       return result;
+   }
+   else
+       return InvalidOid;
+}
index e868d84cef6ac0a88d162c1f10c923ef5ec53531..c97c926992be663d5587b48787080e81ba78f986 100644 (file)
@@ -177,6 +177,7 @@ extern void free_attstatsslot(AttStatsSlot *sslot);
 extern char *get_namespace_name(Oid nspid);
 extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid get_range_subtype(Oid rangeOid);
+extern Oid get_range_collation(Oid rangeOid);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
index 5ed6ae47ec3c632be10044bba1f9666578de921f..220f2d96cbf948d683816188de7ece79aa716e4d 100644 (file)
@@ -582,8 +582,95 @@ select * from numrange_test natural join numrange_test2 order by nr;
 set enable_nestloop to default;
 set enable_hashjoin to default;
 set enable_mergejoin to default;
-DROP TABLE numrange_test;
+-- keep numrange_test around to help exercise dump/reload
 DROP TABLE numrange_test2;
+--
+-- Apply a subset of the above tests on a collatable type, too
+--
+CREATE TABLE textrange_test (tr textrange);
+create index textrange_test_btree on textrange_test(tr);
+INSERT INTO textrange_test VALUES('[,)');
+INSERT INTO textrange_test VALUES('["a",]');
+INSERT INTO textrange_test VALUES('[,"q")');
+INSERT INTO textrange_test VALUES(textrange('b', 'g'));
+INSERT INTO textrange_test VALUES('empty');
+INSERT INTO textrange_test VALUES(textrange('d', 'd', '[]'));
+SELECT tr, isempty(tr), lower(tr), upper(tr) FROM textrange_test;
+  tr   | isempty | lower | upper 
+-------+---------+-------+-------
+ (,)   | f       |       | 
+ [a,)  | f       | a     | 
+ (,q)  | f       |       | q
+ [b,g) | f       | b     | g
+ empty | t       |       | 
+ [d,d] | f       | d     | d
+(6 rows)
+
+SELECT tr, lower_inc(tr), lower_inf(tr), upper_inc(tr), upper_inf(tr) FROM textrange_test;
+  tr   | lower_inc | lower_inf | upper_inc | upper_inf 
+-------+-----------+-----------+-----------+-----------
+ (,)   | f         | t         | f         | t
+ [a,)  | t         | f         | f         | t
+ (,q)  | f         | t         | f         | f
+ [b,g) | t         | f         | f         | f
+ empty | f         | f         | f         | f
+ [d,d] | t         | f         | t         | f
+(6 rows)
+
+SELECT * FROM textrange_test WHERE range_contains(tr, textrange('f', 'fx'));
+  tr   
+-------
+ (,)
+ [a,)
+ (,q)
+ [b,g)
+(4 rows)
+
+SELECT * FROM textrange_test WHERE tr @> textrange('a', 'z');
+  tr  
+------
+ (,)
+ [a,)
+(2 rows)
+
+SELECT * FROM textrange_test WHERE range_contained_by(textrange('0','9'), tr);
+  tr  
+------
+ (,)
+ (,q)
+(2 rows)
+
+SELECT * FROM textrange_test WHERE 'e'::text <@ tr;
+  tr   
+-------
+ (,)
+ [a,)
+ (,q)
+ [b,g)
+(4 rows)
+
+select * from textrange_test where tr = 'empty';
+  tr   
+-------
+ empty
+(1 row)
+
+select * from textrange_test where tr = '("b","g")';
+ tr 
+----
+(0 rows)
+
+select * from textrange_test where tr = '["b","g")';
+  tr   
+-------
+ [b,g)
+(1 row)
+
+select * from textrange_test where tr < 'empty';
+ tr 
+----
+(0 rows)
+
 -- test canonical form for int4range
 select int4range(1, 10, '[]');
  int4range 
index abc765493ba30fbf7b0ebf5fa80b3dd810d746b6..d4e1b9a6e0120651c35e6737f5c8ea7211a5b6cd 100644 (file)
@@ -95,6 +95,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+numrange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -200,6 +201,7 @@ test_range_spgist|t
 test_tsvector|f
 testjsonb|f
 text_tbl|f
+textrange_test|t
 time_tbl|f
 timestamp_tbl|f
 timestamptz_tbl|f
index 2d0ec8964e6768870d2e154176622c8a41cf0f0d..72d80bc9d4662ea319f8eafceea84241588e2f77 100644 (file)
@@ -148,9 +148,37 @@ set enable_nestloop to default;
 set enable_hashjoin to default;
 set enable_mergejoin to default;
 
-DROP TABLE numrange_test;
+-- keep numrange_test around to help exercise dump/reload
 DROP TABLE numrange_test2;
 
+--
+-- Apply a subset of the above tests on a collatable type, too
+--
+
+CREATE TABLE textrange_test (tr textrange);
+create index textrange_test_btree on textrange_test(tr);
+
+INSERT INTO textrange_test VALUES('[,)');
+INSERT INTO textrange_test VALUES('["a",]');
+INSERT INTO textrange_test VALUES('[,"q")');
+INSERT INTO textrange_test VALUES(textrange('b', 'g'));
+INSERT INTO textrange_test VALUES('empty');
+INSERT INTO textrange_test VALUES(textrange('d', 'd', '[]'));
+
+SELECT tr, isempty(tr), lower(tr), upper(tr) FROM textrange_test;
+SELECT tr, lower_inc(tr), lower_inf(tr), upper_inc(tr), upper_inf(tr) FROM textrange_test;
+
+SELECT * FROM textrange_test WHERE range_contains(tr, textrange('f', 'fx'));
+SELECT * FROM textrange_test WHERE tr @> textrange('a', 'z');
+SELECT * FROM textrange_test WHERE range_contained_by(textrange('0','9'), tr);
+SELECT * FROM textrange_test WHERE 'e'::text <@ tr;
+
+select * from textrange_test where tr = 'empty';
+select * from textrange_test where tr = '("b","g")';
+select * from textrange_test where tr = '["b","g")';
+select * from textrange_test where tr < 'empty';
+
+
 -- test canonical form for int4range
 select int4range(1, 10, '[]');
 select int4range(1, 10, '[)');