Skip to content

Commit 703f0ec

Browse files
yahondaclaude
andcommitted
Fix TABLE OF %ROWTYPE parameter in package (fixes #200)
On Oracle 18c+, get_element_definition queries ALL_PLSQL_COLL_TYPES (introduced in 18c) to resolve collection element types. For %ROWTYPE references, Oracle stores elem_type_name as "TABLE_NAME%ROWTYPE". The element was incorrectly treated as OBJECT, causing ensure_tmp_tables_created to embed the type name literally in DDL, producing ORA-00911. Detect the %ROWTYPE suffix in elem_type_name, strip it, verify the table via ALL_OBJECTS, then populate field definitions from table columns. On pre-18c databases (e.g. 11g), this code path is not reached because ALL_ARGUMENTS expands %ROWTYPE into individual fields at deeper data levels. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 412d59e commit 703f0ec

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

lib/plsql/procedure.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,23 @@ def get_element_definition(argument_metadata) # :nodoc:
460460

461461
if elem_type_package != nil
462462
element_metadata[:fields] = get_field_definitions(element_metadata)
463+
elsif elem_type_name && elem_type_name =~ /\A(.+)%ROWTYPE\z/
464+
# TABLE OF table%ROWTYPE: Oracle stores elem_type_name as "TABLE_NAME%ROWTYPE"
465+
rowtype_table_name = $1
466+
check_owner = elem_type_owner || @schema_name
467+
object_type_row = @schema.select_first(
468+
"SELECT object_type FROM ALL_OBJECTS WHERE owner = :owner AND object_name = :name AND object_type IN ('TABLE', 'VIEW')",
469+
check_owner, rowtype_table_name)
470+
if object_type_row
471+
element_metadata[:type_owner] ||= check_owner
472+
element_metadata[:type_name] = rowtype_table_name
473+
element_metadata[:sql_type_name] = build_sql_type_name(check_owner, nil, rowtype_table_name)
474+
element_metadata[:data_type] = "PL/SQL RECORD"
475+
element_metadata[:type_object_type] = object_type_row[0]
476+
element_metadata[:fields] = get_field_definitions(element_metadata)
477+
else
478+
raise ArgumentError, "Could not resolve #{check_owner}.#{rowtype_table_name} to a table or view for #{elem_type_name}"
479+
end
463480
end
464481
when "TYPE"
465482
r = @schema.select_first(

spec/plsql/procedure_spec.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,3 +2426,69 @@ def new_candidate(status)
24262426
expect(field_names.none? { |name| name.to_s.start_with?("sys_nc") }).to be true
24272427
end
24282428
end
2429+
2430+
describe "Function with TABLE OF %ROWTYPE parameter defined in package" do
2431+
before(:all) do
2432+
plsql.connect! CONNECTION_PARAMS
2433+
plsql.execute "DROP PACKAGE test_rowtype_pkg" rescue nil
2434+
plsql.execute "DROP TABLE test_rowtype_tbl" rescue nil
2435+
plsql.execute "CREATE TABLE test_rowtype_tbl (id NUMBER, name VARCHAR2(50))"
2436+
plsql.execute <<-SQL
2437+
CREATE OR REPLACE PACKAGE test_rowtype_pkg IS
2438+
TYPE t_tab IS TABLE OF test_rowtype_tbl%ROWTYPE;
2439+
FUNCTION test_fn(p_tab IN t_tab) RETURN NUMBER;
2440+
END;
2441+
SQL
2442+
plsql.execute <<-SQL
2443+
CREATE OR REPLACE PACKAGE BODY test_rowtype_pkg IS
2444+
FUNCTION test_fn(p_tab IN t_tab) RETURN NUMBER IS
2445+
BEGIN
2446+
RETURN p_tab.COUNT;
2447+
END;
2448+
END;
2449+
SQL
2450+
end
2451+
2452+
after(:all) do
2453+
plsql.execute "DROP PACKAGE test_rowtype_pkg" rescue nil
2454+
plsql.execute "DROP TABLE test_rowtype_tbl" rescue nil
2455+
plsql.logoff
2456+
end
2457+
2458+
it "should execute function with TABLE OF %ROWTYPE parameter" do
2459+
result = plsql.test_rowtype_pkg.test_fn([{ id: 1, name: "test" }])
2460+
expect(result).to eq(1)
2461+
end
2462+
end
2463+
2464+
describe "Function with TABLE OF RECORD parameter defined in package (workaround for %ROWTYPE)" do
2465+
before(:all) do
2466+
plsql.connect! CONNECTION_PARAMS
2467+
plsql.execute "DROP PACKAGE test_record_pkg" rescue nil
2468+
plsql.execute <<-SQL
2469+
CREATE OR REPLACE PACKAGE test_record_pkg IS
2470+
TYPE t_rec IS RECORD (id NUMBER, name VARCHAR2(50));
2471+
TYPE t_tab IS TABLE OF t_rec;
2472+
FUNCTION test_fn(p_tab IN t_tab) RETURN NUMBER;
2473+
END;
2474+
SQL
2475+
plsql.execute <<-SQL
2476+
CREATE OR REPLACE PACKAGE BODY test_record_pkg IS
2477+
FUNCTION test_fn(p_tab IN t_tab) RETURN NUMBER IS
2478+
BEGIN
2479+
RETURN p_tab.COUNT;
2480+
END;
2481+
END;
2482+
SQL
2483+
end
2484+
2485+
after(:all) do
2486+
plsql.execute "DROP PACKAGE test_record_pkg" rescue nil
2487+
plsql.logoff
2488+
end
2489+
2490+
it "should execute function with TABLE OF RECORD parameter" do
2491+
result = plsql.test_record_pkg.test_fn([{ id: 1, name: "test" }])
2492+
expect(result).to eq(1)
2493+
end
2494+
end

0 commit comments

Comments
 (0)