Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions src/pl/plisql/src/expected/plisql_nested_subproc.out
Original file line number Diff line number Diff line change
Expand Up @@ -3954,3 +3954,127 @@ begin
end; $$ language plisql;
DROP TABLE rec_typ2;
DROP TABLE r1;
-- Test for issue #1005: subproc with local variable initialized from parameter
-- This should not crash when the parent function is called
-- Test 1: Package with private procedure that has param-initialized local var
CREATE OR REPLACE PACKAGE test_pkg_1005 IS
FUNCTION get_value RETURN NUMBER;
END test_pkg_1005;
/
CREATE OR REPLACE PACKAGE BODY test_pkg_1005 IS
PROCEDURE private_proc(p_val NUMBER) IS
v_local NUMBER := p_val; -- local var initialized from parameter
BEGIN
NULL; -- private_proc does nothing visible
END private_proc;
FUNCTION get_value RETURN NUMBER IS
BEGIN
RETURN 42;
END get_value;
END test_pkg_1005;
/
-- Should return 42 without crash (private_proc is never called)
SELECT test_pkg_1005.get_value() FROM dual;
get_value
-----------
42
(1 row)

-- Test 2: Call the private procedure to verify it works when actually invoked
CREATE OR REPLACE PACKAGE BODY test_pkg_1005 IS
PROCEDURE private_proc(p_val NUMBER) IS
v_local NUMBER := p_val;
BEGIN
RAISE INFO 'v_local=%', v_local;
END private_proc;
FUNCTION get_value RETURN NUMBER IS
BEGIN
private_proc(100); -- now call private_proc
RETURN 42;
END get_value;
END test_pkg_1005;
/
-- Should print v_local=100 and return 42
SELECT test_pkg_1005.get_value() FROM dual;
INFO: v_local=100
get_value
-----------
42
(1 row)

DROP PACKAGE test_pkg_1005;
-- Test 3: Standalone function with nested procedure (not in package)
CREATE OR REPLACE FUNCTION test_func_1005(p_input NUMBER) RETURN NUMBER AS
v_result NUMBER := 0;
PROCEDURE helper(p_val NUMBER) IS
v_local NUMBER := p_val; -- local var initialized from parameter
BEGIN
v_result := v_local * 2;
END helper;
BEGIN
helper(p_input);
RETURN v_result;
END;
/
-- Should return 200 (100 * 2)
SELECT test_func_1005(100) FROM dual;
test_func_1005
----------------
200
(1 row)

DROP FUNCTION test_func_1005;
-- Test 4: Nested function with param-initialized local var that is never called
CREATE OR REPLACE FUNCTION test_func_1005_nocall(p_input NUMBER) RETURN NUMBER AS
FUNCTION never_called(p_val NUMBER) RETURN NUMBER IS
v_local NUMBER := p_val;
BEGIN
RETURN v_local;
END never_called;
BEGIN
-- never_called is declared but never invoked
RETURN p_input + 1;
END;
/
-- Should return 101 without any issues
SELECT test_func_1005_nocall(100) FROM dual;
test_func_1005_nocall
-----------------------
101
(1 row)

DROP FUNCTION test_func_1005_nocall;
-- Test 5: Multiple nested subprocs with param-initialized local vars
-- Note: func1 is intentionally not called due to issue #1124
CREATE OR REPLACE FUNCTION test_func_1005_multi(p_input NUMBER) RETURN NUMBER AS
v_sum NUMBER := 0;
PROCEDURE proc1(p_val NUMBER) IS
v_local1 NUMBER := p_val;
BEGIN
v_sum := v_sum + v_local1;
END proc1;
PROCEDURE proc2(p_val NUMBER) IS
v_local2 NUMBER := p_val * 2;
BEGIN
v_sum := v_sum + v_local2;
END proc2;
FUNCTION func1(p_val NUMBER) RETURN NUMBER IS
v_local3 NUMBER := p_val * 3;
BEGIN
RETURN v_local3;
END func1;
BEGIN
proc1(10); -- v_sum = 10
proc2(10); -- v_sum = 10 + 20 = 30
-- Not calling: v_sum := v_sum + func1(10) due to issue #1124
RETURN v_sum;
END;
/
-- Should return 30 (proc1 adds 10, proc2 adds 20)
SELECT test_func_1005_multi(10) FROM dual;
test_func_1005_multi
----------------------
30
(1 row)

DROP FUNCTION test_func_1005_multi;
6 changes: 4 additions & 2 deletions src/pl/plisql/src/expected/plisql_type_rowtype.out
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ ERROR: invalid input syntax for type integer: "a"
CONTEXT: PL/iSQL function fun3(pg_catalog.int4) line 1 during statement block local variable initialization
SELECT fun4(4) FROM dual; --failed
ERROR: invalid input syntax for type integer: "a"
CONTEXT: PL/iSQL function fun4(pg_catalog.int4) line 2 during statement block local variable initialization
CONTEXT: PL/iSQL function inf1 line 2 during statement block local variable initialization
PL/iSQL function fun4(pg_catalog.int4) line 7 at RETURN
SELECT fun5(5) FROM dual; --successfully
NOTICE: 5
fun5
Expand Down Expand Up @@ -307,7 +308,8 @@ ERROR: invalid input syntax for type integer: "a"
CONTEXT: PL/iSQL function fun3(pg_catalog.int4) line 1 during statement block local variable initialization
SELECT fun4(4) FROM dual; --failed
ERROR: invalid input syntax for type integer: "a"
CONTEXT: PL/iSQL function fun4(pg_catalog.int4) line 2 during statement block local variable initialization
CONTEXT: PL/iSQL function inf1 line 2 during statement block local variable initialization
PL/iSQL function fun4(pg_catalog.int4) line 7 at RETURN
SELECT fun5(5) FROM dual; --successfully
NOTICE: 5
fun5
Expand Down
54 changes: 54 additions & 0 deletions src/pl/plisql/src/pl_comp.c
Original file line number Diff line number Diff line change
Expand Up @@ -3266,6 +3266,43 @@ plisql_finish_datums(PLiSQL_function * function)
}


/*
* plisql_datum_belongs_to_subproc
* Check if a datum index falls within any subproc's datum range.
*
* Subproc datums (parameters and local variables) should not be initialized
* by the parent block, as they have their own initialization logic.
* Each subproc's datum range is [lastassignvardno, lastoutvardno).
*/
static bool
plisql_datum_belongs_to_subproc(int datum_idx)
{
int j;

for (j = 0; j < plisql_nsubprocFuncs; j++)
{
PLiSQL_subproc_function *subproc = plisql_subprocFuncs[j];

/*
* Skip subprocs that don't have a function body yet (forward declarations).
* Also skip if lastoutvardno is 0 (not yet set).
*/
if (subproc->function == NULL || subproc->lastoutvardno == 0)
continue;

/*
* Check if datum_idx falls within [lastassignvardno, lastoutvardno).
* lastassignvardno is set before building subproc args.
* lastoutvardno is set after the subproc body is fully compiled.
*/
if (datum_idx >= subproc->lastassignvardno &&
datum_idx < subproc->lastoutvardno)
return true;
}

return false;
}

/* ----------
* plisql_add_initdatums Make an array of the datum numbers of
* all the initializable datums created since the last call
Expand All @@ -3292,6 +3329,19 @@ plisql_add_initdatums(int **varnos)
*/
for (i = datums_last; i < plisql_nDatums; i++)
{
/*
* Skip datums that belong to subprocs. These datums (parameters and
* local variables of nested functions/procedures) should not be
* initialized by the parent block. They have their own initialization
* logic within the subproc's execution context.
*
* This prevents crashes when a subproc's local variable has a default
* expression that references the subproc's parameters, which don't
* exist in the parent's execution context.
*/
if (plisql_datum_belongs_to_subproc(i))
continue;

switch (plisql_Datums[i]->dtype)
{
case PLISQL_DTYPE_VAR:
Expand All @@ -3313,6 +3363,10 @@ plisql_add_initdatums(int **varnos)
n = 0;
for (i = datums_last; i < plisql_nDatums; i++)
{
/* Skip subproc datums */
if (plisql_datum_belongs_to_subproc(i))
continue;

switch (plisql_Datums[i]->dtype)
{
case PLISQL_DTYPE_VAR:
Expand Down
120 changes: 120 additions & 0 deletions src/pl/plisql/src/sql/plisql_nested_subproc.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3770,3 +3770,123 @@ end; $$ language plisql;

DROP TABLE rec_typ2;
DROP TABLE r1;

-- Test for issue #1005: subproc with local variable initialized from parameter
-- This should not crash when the parent function is called

-- Test 1: Package with private procedure that has param-initialized local var
CREATE OR REPLACE PACKAGE test_pkg_1005 IS
FUNCTION get_value RETURN NUMBER;
END test_pkg_1005;
/

CREATE OR REPLACE PACKAGE BODY test_pkg_1005 IS
PROCEDURE private_proc(p_val NUMBER) IS
v_local NUMBER := p_val; -- local var initialized from parameter
BEGIN
NULL; -- private_proc does nothing visible
END private_proc;

FUNCTION get_value RETURN NUMBER IS
BEGIN
RETURN 42;
END get_value;
END test_pkg_1005;
/

-- Should return 42 without crash (private_proc is never called)
SELECT test_pkg_1005.get_value() FROM dual;

-- Test 2: Call the private procedure to verify it works when actually invoked
CREATE OR REPLACE PACKAGE BODY test_pkg_1005 IS
PROCEDURE private_proc(p_val NUMBER) IS
v_local NUMBER := p_val;
BEGIN
RAISE INFO 'v_local=%', v_local;
END private_proc;

FUNCTION get_value RETURN NUMBER IS
BEGIN
private_proc(100); -- now call private_proc
RETURN 42;
END get_value;
END test_pkg_1005;
/

-- Should print v_local=100 and return 42
SELECT test_pkg_1005.get_value() FROM dual;

DROP PACKAGE test_pkg_1005;

-- Test 3: Standalone function with nested procedure (not in package)
CREATE OR REPLACE FUNCTION test_func_1005(p_input NUMBER) RETURN NUMBER AS
v_result NUMBER := 0;

PROCEDURE helper(p_val NUMBER) IS
v_local NUMBER := p_val; -- local var initialized from parameter
BEGIN
v_result := v_local * 2;
END helper;
BEGIN
helper(p_input);
RETURN v_result;
END;
/

-- Should return 200 (100 * 2)
SELECT test_func_1005(100) FROM dual;

DROP FUNCTION test_func_1005;

-- Test 4: Nested function with param-initialized local var that is never called
CREATE OR REPLACE FUNCTION test_func_1005_nocall(p_input NUMBER) RETURN NUMBER AS
FUNCTION never_called(p_val NUMBER) RETURN NUMBER IS
v_local NUMBER := p_val;
BEGIN
RETURN v_local;
END never_called;
BEGIN
-- never_called is declared but never invoked
RETURN p_input + 1;
END;
/

-- Should return 101 without any issues
SELECT test_func_1005_nocall(100) FROM dual;

DROP FUNCTION test_func_1005_nocall;

-- Test 5: Multiple nested subprocs with param-initialized local vars
-- Note: func1 is intentionally not called due to issue #1124
CREATE OR REPLACE FUNCTION test_func_1005_multi(p_input NUMBER) RETURN NUMBER AS
v_sum NUMBER := 0;

PROCEDURE proc1(p_val NUMBER) IS
v_local1 NUMBER := p_val;
BEGIN
v_sum := v_sum + v_local1;
END proc1;

PROCEDURE proc2(p_val NUMBER) IS
v_local2 NUMBER := p_val * 2;
BEGIN
v_sum := v_sum + v_local2;
END proc2;

FUNCTION func1(p_val NUMBER) RETURN NUMBER IS
v_local3 NUMBER := p_val * 3;
BEGIN
RETURN v_local3;
END func1;
BEGIN
proc1(10); -- v_sum = 10
proc2(10); -- v_sum = 10 + 20 = 30
-- Not calling: v_sum := v_sum + func1(10) due to issue #1124
RETURN v_sum;
END;
/

-- Should return 30 (proc1 adds 10, proc2 adds 20)
SELECT test_func_1005_multi(10) FROM dual;

DROP FUNCTION test_func_1005_multi;