]> granicus.if.org Git - postgresql/commitdiff
Improve expression evaluation test coverage.
authorAndres Freund <andres@anarazel.de>
Sat, 11 Mar 2017 23:36:50 +0000 (15:36 -0800)
committerAndres Freund <andres@anarazel.de>
Sat, 11 Mar 2017 23:41:34 +0000 (15:41 -0800)
Upcoming patches are revamping expression evaluation significantly. It
therefore seems prudent to try to ensure that the coverage of the
existing evaluation code is high.

This commit adds coverage for the cases that can reasonably be
tested. There's still a bunch of unreachable error messages and such,
but otherwise this achieves nearly full regression test coverage (with
the exception of the unused GetAttributeByNum/GetAttributeByName).

Author: Andres Freund
Discussion: https://postgr.es/m/20170310194021.ek4bs4bl2khxkmll@alap3.anarazel.de

17 files changed:
src/backend/executor/execQual.c
src/test/regress/expected/arrays.out
src/test/regress/expected/boolean.out
src/test/regress/expected/case.out
src/test/regress/expected/expressions.out [new file with mode: 0644]
src/test/regress/expected/inherit.out
src/test/regress/expected/privileges.out
src/test/regress/expected/rowtypes.out
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/arrays.sql
src/test/regress/sql/boolean.sql
src/test/regress/sql/case.sql
src/test/regress/sql/expressions.sql [new file with mode: 0644]
src/test/regress/sql/inherit.sql
src/test/regress/sql/privileges.sql
src/test/regress/sql/rowtypes.sql

index 4566219ca8b0b854038444dfd7427430d1abc896..4ff0188bccf7d92677be3040c2ded024d3df6d4d 100644 (file)
@@ -392,6 +392,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate,
                        }
                        else
                        {
+                               /* this is currently unreachable */
                                econtext->caseValue_datum =
                                        array_get_slice(array_source, i,
                                                                        upper.indx, lower.indx,
index 8c5050577bffb02343f9371e0e605c47ad2e1053..c730563f0386ca593c0f894a9f596de86ab833e2 100644 (file)
@@ -187,6 +187,44 @@ select ('[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
  {{5,6},{8,9}}
 (1 row)
 
+--
+-- check subscription corner cases
+--
+-- More subscripts than MAXDIMS(6)
+SELECT ('{}'::int[])[1][2][3][4][5][6][7];
+ERROR:  number of array dimensions (7) exceeds the maximum allowed (6)
+-- NULL index yields NULL when selecting
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL][1];
+ int4 
+------
+     
+(1 row)
+
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL:1][1];
+ int4 
+------
+(1 row)
+
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][1:NULL][1];
+ int4 
+------
+(1 row)
+
+-- NULL index in assignment is an error
+UPDATE arrtest
+  SET c[NULL] = '{"can''t assign"}'
+  WHERE array_dims(c) is not null;
+ERROR:  array subscript in assignment must not be null
+UPDATE arrtest
+  SET c[NULL:1] = '{"can''t assign"}'
+  WHERE array_dims(c) is not null;
+ERROR:  array subscript in assignment must not be null
+UPDATE arrtest
+  SET c[1:NULL] = '{"can''t assign"}'
+  WHERE array_dims(c) is not null;
+ERROR:  array subscript in assignment must not be null
 -- test slices with empty lower and/or upper index
 CREATE TEMP TABLE arrtest_s (
   a       int2[],
@@ -263,6 +301,36 @@ SELECT f1[:1] FROM POINT_TBL;
 ERROR:  slices of fixed-length arrays not implemented
 SELECT f1[:] FROM POINT_TBL;
 ERROR:  slices of fixed-length arrays not implemented
+-- subscript assignments to fixed-width result in NULL if previous value is NULL
+UPDATE point_tbl SET f1[0] = 10 WHERE f1 IS NULL RETURNING *;
+ f1 
+----
+(1 row)
+
+INSERT INTO point_tbl(f1[0]) VALUES(0) RETURNING *;
+ f1 
+----
+(1 row)
+
+-- NULL assignments get ignored
+UPDATE point_tbl SET f1[0] = NULL WHERE f1::text = '(10,10)'::point::text RETURNING *;
+   f1    
+---------
+ (10,10)
+(1 row)
+
+-- but non-NULL subscript assignments work
+UPDATE point_tbl SET f1[0] = -10, f1[1] = -10 WHERE f1::text = '(10,10)'::point::text RETURNING *;
+    f1     
+-----------
+ (-10,-10)
+(1 row)
+
+-- but not to expand the range
+UPDATE point_tbl SET f1[3] = 10 WHERE f1::text = '(-10,-10)'::point::text RETURNING *;
+ERROR:  array subscript out of range
 --
 -- test array extension
 --
@@ -1099,6 +1167,12 @@ SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}"
  {{{{{{a,bb,ccc}}}}}}
 (1 row)
 
+SELECT NULL::text[]::int[] AS "NULL";
+ NULL 
+------
+(1 row)
+
 -- scalar op any/all (array)
 select 33 = any ('{1,2,3}');
  ?column? 
@@ -1214,6 +1288,13 @@ select 33 = all ('{33,null,33}');
  
 (1 row)
 
+-- nulls later in the bitmap
+SELECT -1 != ALL(ARRAY(SELECT NULLIF(g.i, 900) FROM generate_series(1,1000) g(i)));
+ ?column? 
+----------
+(1 row)
+
 -- test indexes on arrays
 create temp table arr_tbl (f1 int[] unique);
 insert into arr_tbl values ('{1,2,3}');
index 463278dca19ea7a017bbf5014b0208f4ea821dbd..a6e6000c66931aafddde439413b4db75a40a49dc 100644 (file)
@@ -442,6 +442,29 @@ SELECT '' AS "Not True", f1
           | f
 (4 rows)
 
+--
+-- Tests for BooleanTest
+--
+CREATE TABLE BOOLTBL3 (d text, b bool, o int);
+INSERT INTO BOOLTBL3 (d, b, o) VALUES ('true', true, 1);
+INSERT INTO BOOLTBL3 (d, b, o) VALUES ('false', false, 2);
+INSERT INTO BOOLTBL3 (d, b, o) VALUES ('null', null, 3);
+SELECT
+    d,
+    b IS TRUE AS istrue,
+    b IS NOT TRUE AS isnottrue,
+    b IS FALSE AS isfalse,
+    b IS NOT FALSE AS isnotfalse,
+    b IS UNKNOWN AS isunknown,
+    b IS NOT UNKNOWN AS isnotunknown
+FROM booltbl3 ORDER BY o;
+   d   | istrue | isnottrue | isfalse | isnotfalse | isunknown | isnotunknown 
+-------+--------+-----------+---------+------------+-----------+--------------
+ true  | t      | f         | f       | t          | f         | t
+ false | f      | t         | t       | f          | f         | t
+ null  | f      | t         | f       | t          | t         | f
+(3 rows)
+
 --
 -- Clean up
 -- Many tables are retained by the regression test, but these do not seem
@@ -450,3 +473,4 @@ SELECT '' AS "Not True", f1
 --
 DROP TABLE  BOOLTBL1;
 DROP TABLE  BOOLTBL2;
+DROP TABLE  BOOLTBL3;
index 09d5516fb5fa80f7f71274a1008892ba738ed0ad..4cc4851475dd095abf9eda44641f7e1415a67dd6 100644 (file)
@@ -72,6 +72,14 @@ SELECT '6' AS "One",
  6   |                     6
 (1 row)
 
+SELECT '7' AS "None",
+   CASE WHEN random() < 0 THEN 1
+   END AS "NULL on no matches";
+ None | NULL on no matches 
+------+--------------------
+ 7    |                   
+(1 row)
+
 -- Constant-expression folding shouldn't evaluate unreachable subexpressions
 SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END;
  case 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
new file mode 100644 (file)
index 0000000..719455b
--- /dev/null
@@ -0,0 +1,77 @@
+--
+-- expression evaluated tests that don't fit into a more specific file
+--
+--
+-- Tests for SQLVAlueFunction
+--
+-- current_date  (always matches because of transactional behaviour)
+SELECT date(now())::text = current_date::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- current_time / localtime
+SELECT now()::timetz::text = current_time::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT now()::time::text = localtime::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- current_timestamp / localtimestamp (always matches because of transactional behaviour)
+SELECT current_timestamp = NOW();
+ ?column? 
+----------
+ t
+(1 row)
+
+-- precision
+SELECT length(current_timestamp::text) >= length(current_timestamp(0)::text);
+ ?column? 
+----------
+ t
+(1 row)
+
+-- localtimestamp
+SELECT now()::timestamp::text = localtimestamp::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- current_role/user/user is tested in rolnames.sql
+-- current database / catalog
+SELECT current_catalog = current_database();
+ ?column? 
+----------
+ t
+(1 row)
+
+-- current_schema
+SELECT current_schema;
+ current_schema 
+----------------
+ public
+(1 row)
+
+SET search_path = 'notme';
+SELECT current_schema;
+ current_schema 
+----------------
+(1 row)
+
+SET search_path = 'pg_catalog';
+SELECT current_schema;
+ current_schema 
+----------------
+ pg_catalog
+(1 row)
+
+RESET search_path;
index 795d9f575c66c5ee5d9301efb79a9c515a1f3016..6494b205c4dde3553e54263ca347dba8bfb22d21 100644 (file)
@@ -710,6 +710,12 @@ select derived::base from derived;
  (0)
 (1 row)
 
+select NULL::derived::base;
+ base 
+------
+(1 row)
+
 drop table derived;
 drop table base;
 create table p1(ff1 int);
index 8ac46ecef2e57267f53165b958eaeb02230fb059..720675032a566305264aeb6da0f11a9c27b53e73 100644 (file)
@@ -586,6 +586,43 @@ ERROR:  must be owner of function testfunc1
 DROP FUNCTION testfunc1(int); -- ok
 -- restore to sanity
 GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC;
+-- verify privilege checks on coercions
+BEGIN;
+SELECT NULL::int4[]::int8[];
+ int8 
+------
+(1 row)
+
+SELECT '{1}'::int4[]::int8[];
+ int8 
+------
+ {1}
+(1 row)
+
+REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC;
+SELECT NULL::int4[]::int8[];
+ int8 
+------
+(1 row)
+
+SELECT '{1}'::int4[]::int8[]; --superuser, suceed
+ int8 
+------
+ {1}
+(1 row)
+
+SET SESSION AUTHORIZATION regress_user4;
+SELECT NULL::int4[]::int8[];  --other user, no elements to convert
+ int8 
+------
+(1 row)
+
+SELECT '{1}'::int4[]::int8[]; --other user, fail
+ERROR:  permission denied for function int8
+ROLLBACK;
 -- privileges on types
 -- switch to superuser
 \c -
index 25b08281c85b781c6176a81caf5e6055aa1f6532..4acbc9aac8081b6c7e833c320a6d400ba84e4d6f 100644 (file)
@@ -711,3 +711,43 @@ select r, r is null as isnull, r is not null as isnotnull from r;
  (,)         | t      | f
 (6 rows)
 
+--
+-- Tests for component access / FieldSelect
+--
+CREATE TABLE compositetable(a text, b text) WITH OIDS;
+INSERT INTO compositetable(a, b) VALUES('fa', 'fb');
+-- composite type columns can't directly be accessed (error)
+SELECT d.a FROM (SELECT compositetable AS d FROM compositetable) s;
+ERROR:  missing FROM-clause entry for table "d"
+LINE 1: SELECT d.a FROM (SELECT compositetable AS d FROM compositeta...
+               ^
+-- but can be accessed with proper parens
+SELECT (d).a, (d).b FROM (SELECT compositetable AS d FROM compositetable) s;
+ a  | b  
+----+----
+ fa | fb
+(1 row)
+
+-- oids can't be accessed in composite types (error)
+SELECT (d).oid FROM (SELECT compositetable AS d FROM compositetable) s;
+ERROR:  column "oid" not found in data type compositetable
+LINE 1: SELECT (d).oid FROM (SELECT compositetable AS d FROM composi...
+                ^
+-- accessing non-existing column in NULL datum errors out
+SELECT (NULL::compositetable).nonexistant;
+ERROR:  column "nonexistant" not found in data type compositetable
+LINE 1: SELECT (NULL::compositetable).nonexistant;
+                ^
+-- existing column in a NULL composite yield NULL
+SELECT (NULL::compositetable).a;
+ a 
+---
+(1 row)
+
+-- oids can't be accessed in composite types (error)
+SELECT (NULL::compositetable).oid;
+ERROR:  column "oid" not found in data type compositetable
+LINE 1: SELECT (NULL::compositetable).oid;
+                ^
+DROP TABLE compositetable;
index 13bf49445b8a7fb1989ae26654584492dc8b4fcc..9f38349e90f1ae12753ebe7e2ca1652ae8765a7d 100644 (file)
@@ -30,7 +30,7 @@ test: point lseg line box path polygon circle date time timetz timestamp timesta
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz, reltime and abstime
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity
+test: geometry horology regex oidjoins type_sanity opr_sanity expressions
 
 # ----------
 # These four each depend on the previous one
index 645ab9b2dca4d670c845234d74154627daa3ec74..2987b24ebb48e5e23915ca07f41551e4c49517ce 100644 (file)
@@ -49,6 +49,7 @@ test: regex
 test: oidjoins
 test: type_sanity
 test: opr_sanity
+test: expressions
 test: insert
 test: insert_conflict
 test: create_function_1
index 9de720756317b9c01388d4f47cf9bb191f1e3bfe..25dd4e2c6dedd0ea89ad1ecdaf9fd3e9184bcd4d 100644 (file)
@@ -103,6 +103,26 @@ select ('{{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
 select '[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[];
 select ('[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2];
 
+--
+-- check subscription corner cases
+--
+-- More subscripts than MAXDIMS(6)
+SELECT ('{}'::int[])[1][2][3][4][5][6][7];
+-- NULL index yields NULL when selecting
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL][1];
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL:1][1];
+SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][1:NULL][1];
+-- NULL index in assignment is an error
+UPDATE arrtest
+  SET c[NULL] = '{"can''t assign"}'
+  WHERE array_dims(c) is not null;
+UPDATE arrtest
+  SET c[NULL:1] = '{"can''t assign"}'
+  WHERE array_dims(c) is not null;
+UPDATE arrtest
+  SET c[1:NULL] = '{"can''t assign"}'
+  WHERE array_dims(c) is not null;
+
 -- test slices with empty lower and/or upper index
 CREATE TEMP TABLE arrtest_s (
   a       int2[],
@@ -134,6 +154,16 @@ SELECT f1[0:] FROM POINT_TBL;
 SELECT f1[:1] FROM POINT_TBL;
 SELECT f1[:] FROM POINT_TBL;
 
+-- subscript assignments to fixed-width result in NULL if previous value is NULL
+UPDATE point_tbl SET f1[0] = 10 WHERE f1 IS NULL RETURNING *;
+INSERT INTO point_tbl(f1[0]) VALUES(0) RETURNING *;
+-- NULL assignments get ignored
+UPDATE point_tbl SET f1[0] = NULL WHERE f1::text = '(10,10)'::point::text RETURNING *;
+-- but non-NULL subscript assignments work
+UPDATE point_tbl SET f1[0] = -10, f1[1] = -10 WHERE f1::text = '(10,10)'::point::text RETURNING *;
+-- but not to expand the range
+UPDATE point_tbl SET f1[3] = 10 WHERE f1::text = '(-10,-10)'::point::text RETURNING *;
+
 --
 -- test array extension
 --
@@ -316,6 +346,7 @@ SELECT ARRAY[1,2,3]::text[]::int[]::float8[] is of (float8[]) as "TRUE";
 SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] AS "{{a,bc},{def,hijk}}";
 SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] is of (varchar[]) as "TRUE";
 SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}";
+SELECT NULL::text[]::int[] AS "NULL";
 
 -- scalar op any/all (array)
 select 33 = any ('{1,2,3}');
@@ -341,6 +372,8 @@ select 33 = all (null::int[]);
 select null::int = all ('{1,2,3}');
 select 33 = all ('{1,null,3}');
 select 33 = all ('{33,null,33}');
+-- nulls later in the bitmap
+SELECT -1 != ALL(ARRAY(SELECT NULLIF(g.i, 900) FROM generate_series(1,1000) g(i)));
 
 -- test indexes on arrays
 create temp table arr_tbl (f1 int[] unique);
index 5c46fb92fcf8491d8a7a764fa0de18d3f56fdb78..cbf335467b5582c70a9c623ad891a43e0f3e043f 100644 (file)
@@ -201,6 +201,24 @@ SELECT '' AS "Not True", f1
    FROM BOOLTBL2
    WHERE f1 IS NOT TRUE;
 
+--
+-- Tests for BooleanTest
+--
+CREATE TABLE BOOLTBL3 (d text, b bool, o int);
+INSERT INTO BOOLTBL3 (d, b, o) VALUES ('true', true, 1);
+INSERT INTO BOOLTBL3 (d, b, o) VALUES ('false', false, 2);
+INSERT INTO BOOLTBL3 (d, b, o) VALUES ('null', null, 3);
+
+SELECT
+    d,
+    b IS TRUE AS istrue,
+    b IS NOT TRUE AS isnottrue,
+    b IS FALSE AS isfalse,
+    b IS NOT FALSE AS isnotfalse,
+    b IS UNKNOWN AS isunknown,
+    b IS NOT UNKNOWN AS isnotunknown
+FROM booltbl3 ORDER BY o;
+
 --
 -- Clean up
 -- Many tables are retained by the regression test, but these do not seem
@@ -211,3 +229,5 @@ SELECT '' AS "Not True", f1
 DROP TABLE  BOOLTBL1;
 
 DROP TABLE  BOOLTBL2;
+
+DROP TABLE  BOOLTBL3;
index a7ae7b4a9ebfc9f4516a94c142edb8817a6a1d38..59268f8cdfa6a81f476dad1fd2a9765b863cb882 100644 (file)
@@ -58,6 +58,11 @@ SELECT '6' AS "One",
     ELSE 7
   END AS "Two WHEN with default";
 
+
+SELECT '7' AS "None",
+   CASE WHEN random() < 0 THEN 1
+   END AS "NULL on no matches";
+
 -- Constant-expression folding shouldn't evaluate unreachable subexpressions
 SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END;
 SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
new file mode 100644 (file)
index 0000000..3427fdf
--- /dev/null
@@ -0,0 +1,36 @@
+--
+-- expression evaluated tests that don't fit into a more specific file
+--
+
+--
+-- Tests for SQLVAlueFunction
+--
+
+
+-- current_date  (always matches because of transactional behaviour)
+SELECT date(now())::text = current_date::text;
+
+
+-- current_time / localtime
+SELECT now()::timetz::text = current_time::text;
+SELECT now()::time::text = localtime::text;
+
+-- current_timestamp / localtimestamp (always matches because of transactional behaviour)
+SELECT current_timestamp = NOW();
+-- precision
+SELECT length(current_timestamp::text) >= length(current_timestamp(0)::text);
+-- localtimestamp
+SELECT now()::timestamp::text = localtimestamp::text;
+
+-- current_role/user/user is tested in rolnames.sql
+
+-- current database / catalog
+SELECT current_catalog = current_database();
+
+-- current_schema
+SELECT current_schema;
+SET search_path = 'notme';
+SELECT current_schema;
+SET search_path = 'pg_catalog';
+SELECT current_schema;
+RESET search_path;
index 836ec22c204b4ea129d79fbe0d79b1de8c67697b..e3e9e34895b7453ae7ec13bd95ae1aeeb3055523 100644 (file)
@@ -194,6 +194,7 @@ create table base (i integer);
 create table derived () inherits (base);
 insert into derived (i) values (0);
 select derived::base from derived;
+select NULL::derived::base;
 drop table derived;
 drop table base;
 
index 3d74abf043c628096d2bd3e71a9a2fbafc46da59..e3275febea72f7b25f25da1c2c335db40fad608a 100644 (file)
@@ -398,6 +398,18 @@ DROP FUNCTION testfunc1(int); -- ok
 -- restore to sanity
 GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC;
 
+-- verify privilege checks on coercions
+BEGIN;
+SELECT NULL::int4[]::int8[];
+SELECT '{1}'::int4[]::int8[];
+REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC;
+SELECT NULL::int4[]::int8[];
+SELECT '{1}'::int4[]::int8[]; --superuser, suceed
+SET SESSION AUTHORIZATION regress_user4;
+SELECT NULL::int4[]::int8[];  --other user, no elements to convert
+SELECT '{1}'::int4[]::int8[]; --other user, fail
+ROLLBACK;
+
 -- privileges on types
 
 -- switch to superuser
index a62dee2ef8433f8af5238d8254b532cc8af4310e..0d9c62b486d58867a780a73bca7c12a2cb6aaf64 100644 (file)
@@ -310,3 +310,26 @@ with r(a,b) as
   (values (1,row(1,2)), (1,row(null,null)), (1,null),
           (null,row(1,2)), (null,row(null,null)), (null,null) )
 select r, r is null as isnull, r is not null as isnotnull from r;
+
+
+--
+-- Tests for component access / FieldSelect
+--
+CREATE TABLE compositetable(a text, b text) WITH OIDS;
+INSERT INTO compositetable(a, b) VALUES('fa', 'fb');
+
+-- composite type columns can't directly be accessed (error)
+SELECT d.a FROM (SELECT compositetable AS d FROM compositetable) s;
+-- but can be accessed with proper parens
+SELECT (d).a, (d).b FROM (SELECT compositetable AS d FROM compositetable) s;
+-- oids can't be accessed in composite types (error)
+SELECT (d).oid FROM (SELECT compositetable AS d FROM compositetable) s;
+
+-- accessing non-existing column in NULL datum errors out
+SELECT (NULL::compositetable).nonexistant;
+-- existing column in a NULL composite yield NULL
+SELECT (NULL::compositetable).a;
+-- oids can't be accessed in composite types (error)
+SELECT (NULL::compositetable).oid;
+
+DROP TABLE compositetable;