From 51db0d18fbf58b0c2e5ebc2b5b2c48daf45c8d93 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 15 Feb 2018 16:25:19 -0500 Subject: [PATCH] Fix plpgsql to enforce domain checks when returning a NULL domain value. 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 | 2 +- .../plpgsql/src/expected/plpgsql_domain.out | 397 ++++++++++++++++++ src/pl/plpgsql/src/pl_comp.c | 4 + src/pl/plpgsql/src/pl_exec.c | 16 + src/pl/plpgsql/src/plpgsql.h | 1 + src/pl/plpgsql/src/sql/plpgsql_domain.sql | 279 ++++++++++++ 6 files changed, 698 insertions(+), 1 deletion(-) create mode 100644 src/pl/plpgsql/src/expected/plpgsql_domain.out create mode 100644 src/pl/plpgsql/src/sql/plpgsql_domain.sql diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile index 3ac64e2d44..fc60618618 100644 --- a/src/pl/plpgsql/src/Makefile +++ b/src/pl/plpgsql/src/Makefile @@ -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 index 0000000000..efc877cdd1 --- /dev/null +++ b/src/pl/plpgsql/src/expected/plpgsql_domain.out @@ -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) + diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index aab92c4711..d07a16a7ea 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -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); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 5054d20ab1..eae51e316a 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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"); diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index c2449f03cf..26a7344e9a 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -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 index 0000000000..8f99aae5a9 --- /dev/null +++ b/src/pl/plpgsql/src/sql/plpgsql_domain.sql @@ -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