mirror of
https://github.com/postgres/postgres.git
synced 2025-07-09 22:41:56 +03:00
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.
This commit is contained in:
@ -26,7 +26,7 @@ DATA = plpgsql.control plpgsql--1.0.sql plpgsql--unpackaged--1.0.sql
|
|||||||
|
|
||||||
REGRESS_OPTS = --dbname=$(PL_TESTDB)
|
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
|
plpgsql_transaction plpgsql_varprops
|
||||||
|
|
||||||
all: all-lib
|
all: all-lib
|
||||||
|
397
src/pl/plpgsql/src/expected/plpgsql_domain.out
Normal file
397
src/pl/plpgsql/src/expected/plpgsql_domain.out
Normal file
@ -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)
|
||||||
|
|
@ -557,6 +557,7 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
}
|
}
|
||||||
|
|
||||||
function->fn_retistuple = type_is_rowtype(rettypeid);
|
function->fn_retistuple = type_is_rowtype(rettypeid);
|
||||||
|
function->fn_retisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
|
||||||
function->fn_retbyval = typeStruct->typbyval;
|
function->fn_retbyval = typeStruct->typbyval;
|
||||||
function->fn_rettyplen = typeStruct->typlen;
|
function->fn_rettyplen = typeStruct->typlen;
|
||||||
|
|
||||||
@ -584,6 +585,7 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
function->fn_rettype = InvalidOid;
|
function->fn_rettype = InvalidOid;
|
||||||
function->fn_retbyval = false;
|
function->fn_retbyval = false;
|
||||||
function->fn_retistuple = true;
|
function->fn_retistuple = true;
|
||||||
|
function->fn_retisdomain = false;
|
||||||
function->fn_retset = false;
|
function->fn_retset = false;
|
||||||
|
|
||||||
/* shouldn't be any declared arguments */
|
/* shouldn't be any declared arguments */
|
||||||
@ -707,6 +709,7 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
function->fn_rettype = VOIDOID;
|
function->fn_rettype = VOIDOID;
|
||||||
function->fn_retbyval = false;
|
function->fn_retbyval = false;
|
||||||
function->fn_retistuple = true;
|
function->fn_retistuple = true;
|
||||||
|
function->fn_retisdomain = false;
|
||||||
function->fn_retset = false;
|
function->fn_retset = false;
|
||||||
|
|
||||||
/* shouldn't be any declared arguments */
|
/* shouldn't be any declared arguments */
|
||||||
@ -886,6 +889,7 @@ plpgsql_compile_inline(char *proc_source)
|
|||||||
function->fn_rettype = VOIDOID;
|
function->fn_rettype = VOIDOID;
|
||||||
function->fn_retset = false;
|
function->fn_retset = false;
|
||||||
function->fn_retistuple = false;
|
function->fn_retistuple = false;
|
||||||
|
function->fn_retisdomain = false;
|
||||||
/* a bit of hardwired knowledge about type VOID here */
|
/* a bit of hardwired knowledge about type VOID here */
|
||||||
function->fn_retbyval = true;
|
function->fn_retbyval = true;
|
||||||
function->fn_rettyplen = sizeof(int32);
|
function->fn_rettyplen = sizeof(int32);
|
||||||
|
@ -712,6 +712,22 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
|
|||||||
func->fn_rettyplen);
|
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");
|
estate.err_text = gettext_noop("during function exit");
|
||||||
|
|
||||||
|
@ -915,6 +915,7 @@ typedef struct PLpgSQL_function
|
|||||||
int fn_rettyplen;
|
int fn_rettyplen;
|
||||||
bool fn_retbyval;
|
bool fn_retbyval;
|
||||||
bool fn_retistuple;
|
bool fn_retistuple;
|
||||||
|
bool fn_retisdomain;
|
||||||
bool fn_retset;
|
bool fn_retset;
|
||||||
bool fn_readonly;
|
bool fn_readonly;
|
||||||
|
|
||||||
|
279
src/pl/plpgsql/src/sql/plpgsql_domain.sql
Normal file
279
src/pl/plpgsql/src/sql/plpgsql_domain.sql
Normal file
@ -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
|
Reference in New Issue
Block a user