Fix plpgsql to enforce domain checks when returning a NULL domain value.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 15 Feb 2018 21:25:19 +0000 (16:25 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 15 Feb 2018 21:25:19 +0000 (16:25 -0500)
If a plpgsql function is declared to return a domain type, and the domain's
constraints forbid a null value, it was nonetheless possible to return
NULL, because we didn't bother to check the constraints for a null result.
I'd noticed this while fooling with domains-over-composite, but had not
gotten around to fixing it immediately.

Add a regression test script exercising this and various other domain
cases, largely borrowed from the plpython_types test.

Although this is clearly a bug fix, I'm not sure whether anyone would
thank us for changing the behavior in stable branches, so I'm inclined
not to back-patch.

src/pl/plpgsql/src/Makefile
src/pl/plpgsql/src/expected/plpgsql_domain.out [new file with mode: 0644]
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/sql/plpgsql_domain.sql [new file with mode: 0644]

index 3ac64e2d4478f89ea7c21a82194b243550698040..fc6061861850c434c74086ee460e9ffd7d83ff9f 100644 (file)
@@ -26,7 +26,7 @@ DATA = plpgsql.control plpgsql--1.0.sql plpgsql--unpackaged--1.0.sql
 
 REGRESS_OPTS = --dbname=$(PL_TESTDB)
 
-REGRESS = plpgsql_call plpgsql_control plpgsql_record \
+REGRESS = plpgsql_call plpgsql_control plpgsql_domain plpgsql_record \
    plpgsql_transaction plpgsql_varprops
 
 all: all-lib
diff --git a/src/pl/plpgsql/src/expected/plpgsql_domain.out b/src/pl/plpgsql/src/expected/plpgsql_domain.out
new file mode 100644 (file)
index 0000000..efc877c
--- /dev/null
@@ -0,0 +1,397 @@
+--
+-- Tests for PL/pgSQL's behavior with domain types
+--
+CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL);
+CREATE FUNCTION test_argresult_booltrue(x booltrue, y bool) RETURNS booltrue AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_booltrue(true, true);
+ test_argresult_booltrue 
+-------------------------
+ t
+(1 row)
+
+SELECT * FROM test_argresult_booltrue(false, true);
+ERROR:  value for domain booltrue violates check constraint "booltrue_check"
+SELECT * FROM test_argresult_booltrue(true, false);
+ERROR:  value for domain booltrue violates check constraint "booltrue_check"
+CONTEXT:  PL/pgSQL function test_argresult_booltrue(booltrue,boolean) while casting return value to function's return type
+CREATE FUNCTION test_assign_booltrue(x bool, y bool) RETURNS booltrue AS $$
+declare v booltrue := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_booltrue(true, true);
+ test_assign_booltrue 
+----------------------
+ t
+(1 row)
+
+SELECT * FROM test_assign_booltrue(false, true);
+ERROR:  value for domain booltrue violates check constraint "booltrue_check"
+CONTEXT:  PL/pgSQL function test_assign_booltrue(boolean,boolean) line 3 during statement block local variable initialization
+SELECT * FROM test_assign_booltrue(true, false);
+ERROR:  value for domain booltrue violates check constraint "booltrue_check"
+CONTEXT:  PL/pgSQL function test_assign_booltrue(boolean,boolean) line 4 at assignment
+CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0);
+CREATE FUNCTION test_argresult_uint2(x uint2, y int) RETURNS uint2 AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_uint2(100::uint2, 50);
+ test_argresult_uint2 
+----------------------
+                   50
+(1 row)
+
+SELECT * FROM test_argresult_uint2(100::uint2, -50);
+ERROR:  value for domain uint2 violates check constraint "uint2_check"
+CONTEXT:  PL/pgSQL function test_argresult_uint2(uint2,integer) while casting return value to function's return type
+SELECT * FROM test_argresult_uint2(null, 1);
+ test_argresult_uint2 
+----------------------
+                    1
+(1 row)
+
+CREATE FUNCTION test_assign_uint2(x int, y int) RETURNS uint2 AS $$
+declare v uint2 := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_uint2(100, 50);
+ test_assign_uint2 
+-------------------
+                50
+(1 row)
+
+SELECT * FROM test_assign_uint2(100, -50);
+ERROR:  value for domain uint2 violates check constraint "uint2_check"
+CONTEXT:  PL/pgSQL function test_assign_uint2(integer,integer) line 4 at assignment
+SELECT * FROM test_assign_uint2(-100, 50);
+ERROR:  value for domain uint2 violates check constraint "uint2_check"
+CONTEXT:  PL/pgSQL function test_assign_uint2(integer,integer) line 3 during statement block local variable initialization
+SELECT * FROM test_assign_uint2(null, 1);
+ test_assign_uint2 
+-------------------
+                 1
+(1 row)
+
+CREATE DOMAIN nnint AS int NOT NULL;
+CREATE FUNCTION test_argresult_nnint(x nnint, y int) RETURNS nnint AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_nnint(10, 20);
+ test_argresult_nnint 
+----------------------
+                   20
+(1 row)
+
+SELECT * FROM test_argresult_nnint(null, 20);
+ERROR:  domain nnint does not allow null values
+SELECT * FROM test_argresult_nnint(10, null);
+ERROR:  domain nnint does not allow null values
+CONTEXT:  PL/pgSQL function test_argresult_nnint(nnint,integer) while casting return value to function's return type
+CREATE FUNCTION test_assign_nnint(x int, y int) RETURNS nnint AS $$
+declare v nnint := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_nnint(10, 20);
+ test_assign_nnint 
+-------------------
+                20
+(1 row)
+
+SELECT * FROM test_assign_nnint(null, 20);
+ERROR:  domain nnint does not allow null values
+CONTEXT:  PL/pgSQL function test_assign_nnint(integer,integer) line 3 during statement block local variable initialization
+SELECT * FROM test_assign_nnint(10, null);
+ERROR:  domain nnint does not allow null values
+CONTEXT:  PL/pgSQL function test_assign_nnint(integer,integer) line 4 at assignment
+--
+-- Domains over arrays
+--
+CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]);
+CREATE FUNCTION test_argresult_array_domain(x ordered_pair_domain)
+  RETURNS ordered_pair_domain AS $$
+begin
+return x;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_array_domain(ARRAY[0, 100]::ordered_pair_domain);
+ test_argresult_array_domain 
+-----------------------------
+ {0,100}
+(1 row)
+
+SELECT * FROM test_argresult_array_domain(NULL::ordered_pair_domain);
+ test_argresult_array_domain 
+-----------------------------
+(1 row)
+
+CREATE FUNCTION test_argresult_array_domain_check_violation()
+  RETURNS ordered_pair_domain AS $$
+begin
+return array[2,1];
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_array_domain_check_violation();
+ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT:  PL/pgSQL function test_argresult_array_domain_check_violation() while casting return value to function's return type
+CREATE FUNCTION test_assign_ordered_pair_domain(x int, y int, z int) RETURNS ordered_pair_domain AS $$
+declare v ordered_pair_domain := array[x, y];
+begin
+v[2] := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_ordered_pair_domain(1,2,3);
+ test_assign_ordered_pair_domain 
+---------------------------------
+ {1,3}
+(1 row)
+
+SELECT * FROM test_assign_ordered_pair_domain(1,2,0);
+ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT:  PL/pgSQL function test_assign_ordered_pair_domain(integer,integer,integer) line 4 at assignment
+SELECT * FROM test_assign_ordered_pair_domain(2,1,3);
+ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT:  PL/pgSQL function test_assign_ordered_pair_domain(integer,integer,integer) line 3 during statement block local variable initialization
+--
+-- Arrays of domains
+--
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+select test_read_uint2_array(array[1::uint2]);
+ test_read_uint2_array 
+-----------------------
+                     1
+(1 row)
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+begin
+return array[x, x];
+end
+$$ LANGUAGE plpgsql;
+select test_build_uint2_array(1::int2);
+ test_build_uint2_array 
+------------------------
+ {1,1}
+(1 row)
+
+select test_build_uint2_array(-1::int2);  -- fail
+ERROR:  value for domain uint2 violates check constraint "uint2_check"
+CONTEXT:  PL/pgSQL function test_build_uint2_array(smallint) while casting return value to function's return type
+CREATE FUNCTION test_argresult_domain_array(x integer[])
+  RETURNS ordered_pair_domain[] AS $$
+begin
+return array[x::ordered_pair_domain, x::ordered_pair_domain];
+end
+$$ LANGUAGE plpgsql;
+select test_argresult_domain_array(array[2,4]);
+ test_argresult_domain_array 
+-----------------------------
+ {"{2,4}","{2,4}"}
+(1 row)
+
+select test_argresult_domain_array(array[4,2]);  -- fail
+ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT:  PL/pgSQL function test_argresult_domain_array(integer[]) line 3 at RETURN
+CREATE FUNCTION test_argresult_domain_array2(x ordered_pair_domain)
+  RETURNS integer AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+select test_argresult_domain_array2(array[2,4]);
+ test_argresult_domain_array2 
+------------------------------
+                            2
+(1 row)
+
+select test_argresult_domain_array2(array[4,2]);  -- fail
+ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CREATE FUNCTION test_argresult_array_domain_array(x ordered_pair_domain[])
+  RETURNS ordered_pair_domain AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+select test_argresult_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+ test_argresult_array_domain_array 
+-----------------------------------
+ {2,4}
+(1 row)
+
+--
+-- Domains within composite
+--
+CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+CREATE FUNCTION test_result_nnint_container(x int, y int)
+  RETURNS nnint_container AS $$
+begin
+return row(x, y)::nnint_container;
+end
+$$ LANGUAGE plpgsql;
+SELECT test_result_nnint_container(null, 3);
+ test_result_nnint_container 
+-----------------------------
+ (,3)
+(1 row)
+
+SELECT test_result_nnint_container(3, null);  -- fail
+ERROR:  domain nnint does not allow null values
+CONTEXT:  PL/pgSQL function test_result_nnint_container(integer,integer) line 3 at RETURN
+CREATE FUNCTION test_assign_nnint_container(x int, y int, z int)
+  RETURNS nnint_container AS $$
+declare v nnint_container := row(x, y);
+begin
+v.f2 := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_nnint_container(1,2,3);
+ f1 | f2 
+----+----
+  1 |  3
+(1 row)
+
+SELECT * FROM test_assign_nnint_container(1,2,null);
+ERROR:  domain nnint does not allow null values
+CONTEXT:  PL/pgSQL function test_assign_nnint_container(integer,integer,integer) line 4 at assignment
+SELECT * FROM test_assign_nnint_container(1,null,3);
+ERROR:  domain nnint does not allow null values
+CONTEXT:  PL/pgSQL function test_assign_nnint_container(integer,integer,integer) line 3 during statement block local variable initialization
+-- Since core system allows this:
+SELECT null::nnint_container;
+ nnint_container 
+-----------------
+(1 row)
+
+-- so should PL/PgSQL
+CREATE FUNCTION test_assign_nnint_container2(x int, y int, z int)
+  RETURNS nnint_container AS $$
+declare v nnint_container;
+begin
+v.f2 := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_nnint_container2(1,2,3);
+ f1 | f2 
+----+----
+    |  3
+(1 row)
+
+SELECT * FROM test_assign_nnint_container2(1,2,null);
+ERROR:  domain nnint does not allow null values
+CONTEXT:  PL/pgSQL function test_assign_nnint_container2(integer,integer,integer) line 4 at assignment
+--
+-- Domains of composite
+--
+CREATE TYPE named_pair AS (
+    i integer,
+    j integer
+);
+CREATE DOMAIN ordered_named_pair AS named_pair CHECK((VALUE).i <= (VALUE).j);
+CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+begin
+return p.i + p.j;
+end
+$$ LANGUAGE plpgsql;
+SELECT read_ordered_named_pair(row(1, 2));
+ read_ordered_named_pair 
+-------------------------
+                       3
+(1 row)
+
+SELECT read_ordered_named_pair(row(2, 1));  -- fail
+ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+begin
+return row(i, j);
+end
+$$ LANGUAGE plpgsql;
+SELECT build_ordered_named_pair(1,2);
+ build_ordered_named_pair 
+--------------------------
+ (1,2)
+(1 row)
+
+SELECT build_ordered_named_pair(2,1);  -- fail
+ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT:  PL/pgSQL function build_ordered_named_pair(integer,integer) while casting return value to function's return type
+CREATE FUNCTION test_assign_ordered_named_pair(x int, y int, z int)
+  RETURNS ordered_named_pair AS $$
+declare v ordered_named_pair := row(x, y);
+begin
+v.j := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_ordered_named_pair(1,2,3);
+ i | j 
+---+---
+ 1 | 3
+(1 row)
+
+SELECT * FROM test_assign_ordered_named_pair(1,2,0);
+ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT:  PL/pgSQL function test_assign_ordered_named_pair(integer,integer,integer) line 4 at assignment
+SELECT * FROM test_assign_ordered_named_pair(2,1,3);
+ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT:  PL/pgSQL function test_assign_ordered_named_pair(integer,integer,integer) line 3 during statement block local variable initialization
+CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+begin
+return array[row(i, j), row(i, j+1)];
+end
+$$ LANGUAGE plpgsql;
+SELECT build_ordered_named_pairs(1,2);
+ build_ordered_named_pairs 
+---------------------------
+ {"(1,2)","(1,3)"}
+(1 row)
+
+SELECT build_ordered_named_pairs(2,1);  -- fail
+ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT:  PL/pgSQL function build_ordered_named_pairs(integer,integer) while casting return value to function's return type
+CREATE FUNCTION test_assign_ordered_named_pairs(x int, y int, z int)
+  RETURNS ordered_named_pair[] AS $$
+declare v ordered_named_pair[] := array[row(x, y)];
+begin
+-- ideally this would work, but it doesn't yet:
+-- v[1].j := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_ordered_named_pairs(1,2,3);
+ test_assign_ordered_named_pairs 
+---------------------------------
+ {"(1,2)"}
+(1 row)
+
+SELECT * FROM test_assign_ordered_named_pairs(2,1,3);
+ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT:  PL/pgSQL function test_assign_ordered_named_pairs(integer,integer,integer) line 3 during statement block local variable initialization
+SELECT * FROM test_assign_ordered_named_pairs(1,2,0);  -- should fail someday
+ test_assign_ordered_named_pairs 
+---------------------------------
+ {"(1,2)"}
+(1 row)
+
index aab92c4711c5de8385d749c84d4ae94c86c7ddff..d07a16a7ea3637bf2ce06fb6cd51de13b78bc0ad 100644 (file)
@@ -557,6 +557,7 @@ do_compile(FunctionCallInfo fcinfo,
                }
 
                function->fn_retistuple = type_is_rowtype(rettypeid);
+               function->fn_retisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
                function->fn_retbyval = typeStruct->typbyval;
                function->fn_rettyplen = typeStruct->typlen;
 
@@ -584,6 +585,7 @@ do_compile(FunctionCallInfo fcinfo,
            function->fn_rettype = InvalidOid;
            function->fn_retbyval = false;
            function->fn_retistuple = true;
+           function->fn_retisdomain = false;
            function->fn_retset = false;
 
            /* shouldn't be any declared arguments */
@@ -707,6 +709,7 @@ do_compile(FunctionCallInfo fcinfo,
            function->fn_rettype = VOIDOID;
            function->fn_retbyval = false;
            function->fn_retistuple = true;
+           function->fn_retisdomain = false;
            function->fn_retset = false;
 
            /* shouldn't be any declared arguments */
@@ -886,6 +889,7 @@ plpgsql_compile_inline(char *proc_source)
    function->fn_rettype = VOIDOID;
    function->fn_retset = false;
    function->fn_retistuple = false;
+   function->fn_retisdomain = false;
    /* a bit of hardwired knowledge about type VOID here */
    function->fn_retbyval = true;
    function->fn_rettyplen = sizeof(int32);
index 5054d20ab152f8285509530679897b13542df05c..eae51e316a9519f40057c953b66b026cbc488cab 100644 (file)
@@ -712,6 +712,22 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
                                                  func->fn_rettyplen);
        }
    }
+   else
+   {
+       /*
+        * We're returning a NULL, which normally requires no conversion work
+        * regardless of datatypes.  But, if we are casting it to a domain
+        * return type, we'd better check that the domain's constraints pass.
+        */
+       if (func->fn_retisdomain)
+           estate.retval = exec_cast_value(&estate,
+                                           estate.retval,
+                                           &fcinfo->isnull,
+                                           estate.rettype,
+                                           -1,
+                                           func->fn_rettype,
+                                           -1);
+   }
 
    estate.err_text = gettext_noop("during function exit");
 
index c2449f03cf9ea1b7ab4159ac9727c95f99cef4fb..26a7344e9a6c829e73bbf1769c03c77cfab6f7bc 100644 (file)
@@ -915,6 +915,7 @@ typedef struct PLpgSQL_function
    int         fn_rettyplen;
    bool        fn_retbyval;
    bool        fn_retistuple;
+   bool        fn_retisdomain;
    bool        fn_retset;
    bool        fn_readonly;
 
diff --git a/src/pl/plpgsql/src/sql/plpgsql_domain.sql b/src/pl/plpgsql/src/sql/plpgsql_domain.sql
new file mode 100644 (file)
index 0000000..8f99aae
--- /dev/null
@@ -0,0 +1,279 @@
+--
+-- Tests for PL/pgSQL's behavior with domain types
+--
+
+CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL);
+
+CREATE FUNCTION test_argresult_booltrue(x booltrue, y bool) RETURNS booltrue AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_booltrue(true, true);
+SELECT * FROM test_argresult_booltrue(false, true);
+SELECT * FROM test_argresult_booltrue(true, false);
+
+CREATE FUNCTION test_assign_booltrue(x bool, y bool) RETURNS booltrue AS $$
+declare v booltrue := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_booltrue(true, true);
+SELECT * FROM test_assign_booltrue(false, true);
+SELECT * FROM test_assign_booltrue(true, false);
+
+
+CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0);
+
+CREATE FUNCTION test_argresult_uint2(x uint2, y int) RETURNS uint2 AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_uint2(100::uint2, 50);
+SELECT * FROM test_argresult_uint2(100::uint2, -50);
+SELECT * FROM test_argresult_uint2(null, 1);
+
+CREATE FUNCTION test_assign_uint2(x int, y int) RETURNS uint2 AS $$
+declare v uint2 := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_uint2(100, 50);
+SELECT * FROM test_assign_uint2(100, -50);
+SELECT * FROM test_assign_uint2(-100, 50);
+SELECT * FROM test_assign_uint2(null, 1);
+
+
+CREATE DOMAIN nnint AS int NOT NULL;
+
+CREATE FUNCTION test_argresult_nnint(x nnint, y int) RETURNS nnint AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_nnint(10, 20);
+SELECT * FROM test_argresult_nnint(null, 20);
+SELECT * FROM test_argresult_nnint(10, null);
+
+CREATE FUNCTION test_assign_nnint(x int, y int) RETURNS nnint AS $$
+declare v nnint := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_nnint(10, 20);
+SELECT * FROM test_assign_nnint(null, 20);
+SELECT * FROM test_assign_nnint(10, null);
+
+
+--
+-- Domains over arrays
+--
+
+CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]);
+
+CREATE FUNCTION test_argresult_array_domain(x ordered_pair_domain)
+  RETURNS ordered_pair_domain AS $$
+begin
+return x;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_array_domain(ARRAY[0, 100]::ordered_pair_domain);
+SELECT * FROM test_argresult_array_domain(NULL::ordered_pair_domain);
+
+CREATE FUNCTION test_argresult_array_domain_check_violation()
+  RETURNS ordered_pair_domain AS $$
+begin
+return array[2,1];
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_array_domain_check_violation();
+
+CREATE FUNCTION test_assign_ordered_pair_domain(x int, y int, z int) RETURNS ordered_pair_domain AS $$
+declare v ordered_pair_domain := array[x, y];
+begin
+v[2] := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_ordered_pair_domain(1,2,3);
+SELECT * FROM test_assign_ordered_pair_domain(1,2,0);
+SELECT * FROM test_assign_ordered_pair_domain(2,1,3);
+
+
+--
+-- Arrays of domains
+--
+
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+
+select test_read_uint2_array(array[1::uint2]);
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+begin
+return array[x, x];
+end
+$$ LANGUAGE plpgsql;
+
+select test_build_uint2_array(1::int2);
+select test_build_uint2_array(-1::int2);  -- fail
+
+CREATE FUNCTION test_argresult_domain_array(x integer[])
+  RETURNS ordered_pair_domain[] AS $$
+begin
+return array[x::ordered_pair_domain, x::ordered_pair_domain];
+end
+$$ LANGUAGE plpgsql;
+
+select test_argresult_domain_array(array[2,4]);
+select test_argresult_domain_array(array[4,2]);  -- fail
+
+CREATE FUNCTION test_argresult_domain_array2(x ordered_pair_domain)
+  RETURNS integer AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+
+select test_argresult_domain_array2(array[2,4]);
+select test_argresult_domain_array2(array[4,2]);  -- fail
+
+CREATE FUNCTION test_argresult_array_domain_array(x ordered_pair_domain[])
+  RETURNS ordered_pair_domain AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+
+select test_argresult_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+
+
+--
+-- Domains within composite
+--
+
+CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+
+CREATE FUNCTION test_result_nnint_container(x int, y int)
+  RETURNS nnint_container AS $$
+begin
+return row(x, y)::nnint_container;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT test_result_nnint_container(null, 3);
+SELECT test_result_nnint_container(3, null);  -- fail
+
+CREATE FUNCTION test_assign_nnint_container(x int, y int, z int)
+  RETURNS nnint_container AS $$
+declare v nnint_container := row(x, y);
+begin
+v.f2 := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_nnint_container(1,2,3);
+SELECT * FROM test_assign_nnint_container(1,2,null);
+SELECT * FROM test_assign_nnint_container(1,null,3);
+
+-- Since core system allows this:
+SELECT null::nnint_container;
+-- so should PL/PgSQL
+
+CREATE FUNCTION test_assign_nnint_container2(x int, y int, z int)
+  RETURNS nnint_container AS $$
+declare v nnint_container;
+begin
+v.f2 := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_nnint_container2(1,2,3);
+SELECT * FROM test_assign_nnint_container2(1,2,null);
+
+
+--
+-- Domains of composite
+--
+
+CREATE TYPE named_pair AS (
+    i integer,
+    j integer
+);
+
+CREATE DOMAIN ordered_named_pair AS named_pair CHECK((VALUE).i <= (VALUE).j);
+
+CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+begin
+return p.i + p.j;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT read_ordered_named_pair(row(1, 2));
+SELECT read_ordered_named_pair(row(2, 1));  -- fail
+
+CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+begin
+return row(i, j);
+end
+$$ LANGUAGE plpgsql;
+
+SELECT build_ordered_named_pair(1,2);
+SELECT build_ordered_named_pair(2,1);  -- fail
+
+CREATE FUNCTION test_assign_ordered_named_pair(x int, y int, z int)
+  RETURNS ordered_named_pair AS $$
+declare v ordered_named_pair := row(x, y);
+begin
+v.j := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_ordered_named_pair(1,2,3);
+SELECT * FROM test_assign_ordered_named_pair(1,2,0);
+SELECT * FROM test_assign_ordered_named_pair(2,1,3);
+
+CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+begin
+return array[row(i, j), row(i, j+1)];
+end
+$$ LANGUAGE plpgsql;
+
+SELECT build_ordered_named_pairs(1,2);
+SELECT build_ordered_named_pairs(2,1);  -- fail
+
+CREATE FUNCTION test_assign_ordered_named_pairs(x int, y int, z int)
+  RETURNS ordered_named_pair[] AS $$
+declare v ordered_named_pair[] := array[row(x, y)];
+begin
+-- ideally this would work, but it doesn't yet:
+-- v[1].j := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_ordered_named_pairs(1,2,3);
+SELECT * FROM test_assign_ordered_named_pairs(2,1,3);
+SELECT * FROM test_assign_ordered_named_pairs(1,2,0);  -- should fail someday