Skip to content

Commit 7cd0c26

Browse files
yahondaclaude
andcommitted
Resolve package SUBTYPE variables to their base types (rsim#223)
When a package variable is declared with a SUBTYPE (e.g., SUBTYPE num_type IS NUMBER(38,0)), accessing the variable raised ArgumentError because the SUBTYPE name was not recognized. Resolve SUBTYPEs to their base types by querying ALL_SOURCE for the SUBTYPE definition. Handles both unqualified (SUBTYPE) and package-qualified (PACKAGE.SUBTYPE) type names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c3fa632 commit 7cd0c26

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

lib/plsql/variable.rb

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ def metadata(type_string)
7474
end
7575
{ data_type: typecode, data_length: nil, sql_type_name: "#{type.schema_name}.#{type.type_name}", in_out: "IN/OUT" }
7676
rescue ArgumentError
77-
raise ArgumentError, "Package variable data type #{type_string} is not object type defined in schema"
77+
# Try to resolve as a package SUBTYPE
78+
base_type = resolve_package_subtype(type_string)
79+
if base_type
80+
metadata(base_type)
81+
else
82+
raise ArgumentError, "Package variable data type #{type_string} is not object type defined in schema"
83+
end
7884
end
7985
when /^(\w+\.)?(\w+)%ROWTYPE$/
8086
schema = $1 ? plsql.send($1.chop) : plsql
@@ -95,6 +101,34 @@ def metadata(type_string)
95101
end
96102
end
97103

104+
# Resolve a package SUBTYPE to its base type by querying ALL_SOURCE.
105+
# Handles both qualified (PACKAGE.SUBTYPE) and unqualified (SUBTYPE) names.
106+
def resolve_package_subtype(type_string)
107+
if type_string =~ /^(\w+)\.(\w+)$/i
108+
# Package-qualified: PACKAGE.SUBTYPE
109+
package_name = $1.upcase
110+
subtype_name = $2.upcase
111+
elsif type_string =~ /^(\w+)$/i
112+
# Unqualified: look up in the current package
113+
package_name = @package_name
114+
subtype_name = $1.upcase
115+
else
116+
return nil
117+
end
118+
119+
@schema.select_all(
120+
"SELECT text FROM all_source
121+
WHERE owner = :owner AND name = :package_name AND type = 'PACKAGE'
122+
AND UPPER(text) LIKE :subtype_pattern",
123+
@schema_name, package_name, "%SUBTYPE%#{subtype_name}%"
124+
).each do |row|
125+
if row[0] =~ /^\s*SUBTYPE\s+#{subtype_name}\s+IS\s+([^;]+?)\s*(NOT\s+NULL\s*)?;\s*(--.*)?$/i
126+
return $1.strip
127+
end
128+
end
129+
nil
130+
end
131+
98132
# wrapper class to simulate Procedure class for ProcedureClass#exec
99133
class VariableProcedure # :nodoc:
100134
attr_reader :arguments, :argument_list, :return, :out_list, :schema

spec/plsql/variable_spec.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,43 @@
340340

341341
end
342342

343+
describe "package subtypes" do
344+
before(:all) do
345+
plsql.connect! CONNECTION_PARAMS
346+
plsql.execute "DROP PACKAGE test_subtype_pkg" rescue nil
347+
plsql.execute <<-SQL
348+
CREATE OR REPLACE PACKAGE test_subtype_pkg IS
349+
SUBTYPE num_type IS NUMBER(38,0) NOT NULL;
350+
SUBTYPE str_type IS VARCHAR2(100);
351+
num_var num_type := 42;
352+
str_var str_type := 'hello';
353+
qualified_var test_subtype_pkg.num_type := 99;
354+
END;
355+
SQL
356+
plsql.execute <<-SQL
357+
CREATE OR REPLACE PACKAGE BODY test_subtype_pkg IS
358+
END;
359+
SQL
360+
end
361+
362+
after(:all) do
363+
plsql.execute "DROP PACKAGE test_subtype_pkg" rescue nil
364+
plsql.logoff
365+
end
366+
367+
it "should get variable with unqualified subtype" do
368+
expect(plsql.test_subtype_pkg.num_var).to eq(42)
369+
end
370+
371+
it "should get VARCHAR2 variable with unqualified subtype" do
372+
expect(plsql.test_subtype_pkg.str_var).to eq("hello")
373+
end
374+
375+
it "should get variable with package-qualified subtype" do
376+
expect(plsql.test_subtype_pkg.qualified_var).to eq(99)
377+
end
378+
end
379+
343380
describe "constants with multiline declaration" do
344381
before(:all) do
345382
plsql.connect! CONNECTION_PARAMS

0 commit comments

Comments
 (0)