Skip to content

Improve 18c support #208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
513ea0a
Try to resolve package synonyms like utl_file.file_type
joschug Oct 11, 2021
4c5959d
Field definitions do not necessarily have to come from the current sc…
joschug Oct 11, 2021
38bd95c
Split lookup against all_objects into second query; alleviates the po…
joschug Oct 11, 2021
1f328bf
Field definitions do not necessarily have to come from the current sc…
joschug Oct 11, 2021
6befbcc
Try to resolve type synonyms - also for elements and table/view types
joschug Oct 11, 2021
244bcd6
Allow variable declarations that do not end with a semicolon
joschug Oct 12, 2021
00ebb3d
18c: Resolve %ROWTYPE within an element
joschug Oct 13, 2021
631aa40
Merge branch '18c_allow_field_definitions_from_other_schemas' into dev
joschug Oct 13, 2021
afab0c1
Merge branch '18c_public_plsql_records' into dev
joschug Oct 13, 2021
700b418
Merge branch '18c_variables_without_semicolon_at_the_end_of_the_line'…
joschug Oct 13, 2021
907f516
Merge branch '18c_resolve_element_rowtype' into dev
joschug Oct 13, 2021
de8b2e5
Exclude hidden columns
joschug Oct 13, 2021
54429b5
Fix "no implicit conversion of Time into String" if column data type is
joschug Oct 13, 2021
d0d5beb
18c: Resolve %ROWTYPE within an element
joschug Oct 13, 2021
dfcdc33
Merge branch '18c_resolve_element_rowtype' into dev
joschug Oct 13, 2021
2eedc94
Make char length determination consistent
joschug Oct 13, 2021
403ef6a
Try to fix nested types - type declared inside a package, referencing…
joschug Oct 14, 2021
cbdc13f
Map new PL/SQL types to old types
joschug Oct 15, 2021
0409a09
Remove debug output
joschug Oct 20, 2021
89e4af9
Merge branch '18c_public_plsql_records' into dev
joschug Oct 20, 2021
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
202 changes: 152 additions & 50 deletions lib/plsql/procedure.rb
Original file line number Diff line number Diff line change
@@ -160,7 +160,7 @@ def get_argument_metadata_below_18c #:nodoc:
data_precision: data_precision && data_precision.to_i,
data_scale: data_scale && data_scale.to_i,
char_used: char_used,
char_length: char_length && char_length.to_i,
char_length: char_used && char_length && char_length.to_i,
type_owner: type_owner,
type_name: type_name,
type_subname: type_subname,
@@ -219,21 +219,24 @@ def get_argument_metadata_from_18c #:nodoc:
@tmp_tables_created = {}

@schema.select_all(
"SELECT subprogram_id, object_name, TO_NUMBER(overload), argument_name, position,
data_type, in_out, data_length, data_precision, data_scale, char_used,
char_length, type_owner, nvl(type_subname, type_name),
decode(type_object_type, 'PACKAGE', type_name, null), type_object_type, defaulted
FROM all_arguments
WHERE object_id = :object_id
AND owner = :owner
AND object_name = :procedure_name
ORDER BY overload, sequence",
"SELECT a.subprogram_id, a.object_name, TO_NUMBER(a.overload), a.argument_name, a.position,
a.data_type, a.in_out, a.data_length, a.data_precision, a.data_scale, a.char_used,
a.char_length, a.type_owner, nvl(a.type_subname, a.type_name) type_name,
case when a.type_object_type = 'PACKAGE' then a.type_name end type_package, a.type_object_type, a.defaulted,
s.table_owner synonym_owner, s.table_name synonym_name
FROM all_arguments a
LEFT JOIN all_synonyms s ON a.type_owner = s.owner AND a.type_name = s.synonym_name
WHERE a.object_id = :object_id
AND a.owner = :owner
AND a.object_name = :procedure_name
ORDER BY a.overload, a.sequence",
@object_id, @schema_name, @procedure
) do |r|

subprogram_id, object_name, overload, argument_name, position,
data_type, in_out, data_length, data_precision, data_scale, char_used,
char_length, type_owner, type_name, type_package, type_object_type, defaulted = r
char_length, type_owner, type_name, type_package, type_object_type, defaulted,
synonym_dest_owner, synonym_dest_name = r

@overloaded ||= !overload.nil?
# if not overloaded then store arguments at key 0
@@ -242,6 +245,24 @@ def get_argument_metadata_from_18c #:nodoc:
@return[overload] ||= nil
@tmp_table_names[overload] ||= []

unless synonym_dest_owner.nil?
@schema.select_all(
"SELECT o.owner, o.object_name, o.object_type
FROM all_objects o
WHERE o.owner = :synonym_owner
AND o.object_name = :synonym_name
AND o.object_type IN ('PACKAGE', 'TYPE')",
synonym_dest_owner, synonym_dest_name
) do |r2|
tmp_owner, tmp_name, tmp_type = r2
if tmp_type == 'PACKAGE'
type_owner, type_package, type_object_type = tmp_owner, tmp_name, tmp_type
else
type_owner, type_name, type_object_type = tmp_owner, tmp_name, tmp_type
end
end
end

sql_type_name = build_sql_type_name(type_owner, type_package, type_name)

tmp_table_name = nil
@@ -260,7 +281,7 @@ def get_argument_metadata_from_18c #:nodoc:
data_precision: data_precision && data_precision.to_i,
data_scale: data_scale && data_scale.to_i,
char_used: char_used,
char_length: char_length && char_length.to_i,
char_length: char_used && char_length && char_length.to_i,
type_owner: type_owner,
type_name: type_name,
# TODO: should be renamed to type_package, when support for legacy database versions is dropped
@@ -350,18 +371,35 @@ def get_field_definitions(argument_metadata) #:nodoc:
case argument_metadata[:type_object_type]
when "PACKAGE"
@schema.select_all(
"SELECT attr_no, attr_name, attr_type_owner, attr_type_name, attr_type_package, length, precision, scale, char_used
FROM ALL_PLSQL_TYPES t, ALL_PLSQL_TYPE_ATTRS ta
WHERE t.OWNER = :owner AND t.type_name = :type_name AND t.package_name = :package_name
AND ta.OWNER = t.owner AND ta.TYPE_NAME = t.TYPE_NAME AND ta.PACKAGE_NAME = t.PACKAGE_NAME
ORDER BY attr_no",
@schema_name, argument_metadata[:type_name], argument_metadata[:type_subname]) do |r|
"SELECT ta.attr_no, ta.attr_name,
nvl(s.table_owner, attr_type_owner) attr_type_owner,
CASE WHEN ta.attr_type_package IS NOT NULL THEN ta.attr_type_name ELSE nvl(s.table_name, ta.attr_type_name) END attr_type_name,
CASE WHEN ta.attr_type_package IS NOT NULL THEN nvl(s.table_name, ta.attr_type_package) END attr_type_package,
ta.length, ta.precision, ta.scale, ta.char_used
FROM all_plsql_type_attrs ta
LEFT JOIN all_synonyms s ON ta.attr_type_owner = s.owner AND nvl(ta.attr_type_package, ta.attr_type_name) = s.synonym_name
WHERE ta.owner = :owner
AND ta.type_name = :type_name
AND ta.package_name = :package_name
ORDER BY ta.attr_no",
argument_metadata[:type_owner], argument_metadata[:type_name], argument_metadata[:type_subname]) do |r|

attr_no, attr_name, attr_type_owner, attr_type_name, attr_type_package, attr_length, attr_precision, attr_scale, attr_char_used = r

attr_type_name = 'PLS_INTEGER' if attr_type_name == 'PL/SQL PLS INTEGER'
attr_type_name = 'BINARY_INTEGER' if attr_type_name == 'PL/SQL BINARY INTEGER'

composite_type = nil
if attr_type_owner != nil
if attr_type_package != nil
composite_type = get_composite_type(attr_type_owner, attr_type_name, attr_type_package)
else
composite_type = 'TABLE'
end
end
fields[attr_name.downcase.to_sym] = {
position: attr_no.to_i,
data_type: attr_type_owner == nil ? attr_type_name : get_composite_type(attr_type_owner, attr_type_name, attr_type_package),
data_type: attr_type_owner == nil ? attr_type_name : composite_type,
in_out: argument_metadata[:in_out],
data_length: attr_length && attr_length.to_i,
data_precision: attr_precision && attr_precision.to_i,
@@ -372,8 +410,12 @@ def get_field_definitions(argument_metadata) #:nodoc:
type_name: attr_type_owner && attr_type_name,
type_subname: attr_type_package,
sql_type_name: attr_type_owner && build_sql_type_name(attr_type_owner, attr_type_package, attr_type_name),
defaulted: argument_metadata[:defaulted]
defaulted: argument_metadata[:defaulted],
type_object_type: composite_type && composite_type == 'TABLE' ? 'TYPE' : nil
}
if composite_type == 'TABLE'
fields[attr_name.downcase.to_sym][:element] = get_element_definition(fields[attr_name.downcase.to_sym])
end

if fields[attr_name.downcase.to_sym][:data_type] == "TABLE" && fields[attr_name.downcase.to_sym][:type_subname] != nil
fields[attr_name.downcase.to_sym][:fields] = get_field_definitions(fields[attr_name.downcase.to_sym])
@@ -383,11 +425,15 @@ def get_field_definitions(argument_metadata) #:nodoc:
@schema.select_all(
"SELECT column_id, column_name, data_type, data_length, data_precision, data_scale, char_length, char_used
FROM ALL_TAB_COLS WHERE OWNER = :owner AND TABLE_NAME = :type_name
AND hidden_column != 'YES'
ORDER BY column_id",
@schema_name, argument_metadata[:type_name]) do |r|
argument_metadata[:type_owner], argument_metadata[:type_name]) do |r|

col_no, col_name, col_type_name, col_length, col_precision, col_scale, col_char_length, col_char_used = r

if col_type_name.match('TIMESTAMP\(\d+\) WITH LOCAL TIME ZONE')
col_type_name = 'TIMESTAMP WITH LOCAL TIME ZONE'
end
fields[col_name.downcase.to_sym] = {
position: col_no.to_i,
data_type: col_type_name,
@@ -396,7 +442,7 @@ def get_field_definitions(argument_metadata) #:nodoc:
data_precision: col_precision && col_precision.to_i,
data_scale: col_scale && col_scale.to_i,
char_used: col_char_used == nil ? "0" : col_char_used,
char_length: col_char_length && col_char_length.to_i,
char_length: col_char_used && col_char_length && col_char_length.to_i,
type_owner: nil,
type_name: nil,
type_subname: nil,
@@ -414,47 +460,103 @@ def get_element_definition(argument_metadata) #:nodoc:
case argument_metadata[:type_object_type]
when "PACKAGE"
r = @schema.select_first(
"SELECT elem_type_owner, elem_type_name, elem_type_package, length, precision, scale, char_used, index_by
FROM ALL_PLSQL_COLL_TYPES t
WHERE t.OWNER = :owner AND t.TYPE_NAME = :type_name AND t.PACKAGE_NAME = :package_name",
@schema_name, argument_metadata[:type_name], argument_metadata[:type_subname])
"SELECT nvl(s.table_owner, t.elem_type_owner) elem_type_owner,
CASE WHEN t.elem_type_package IS NOT NULL THEN t.elem_type_name ELSE nvl(s.table_name, t.elem_type_name) END elem_type_name,
CASE WHEN t.elem_type_package IS NOT NULL THEN nvl(s.table_name, t.elem_type_package) END elem_type_package_new,
length, precision, scale, char_used, index_by
FROM all_plsql_coll_types t
LEFT JOIN all_synonyms s ON t.elem_type_owner = s.owner AND nvl(t.elem_type_package, t.elem_type_name) = s.synonym_name
WHERE t.owner = :owner
AND t.type_name = :type_name
AND t.package_name = :package_name",
argument_metadata[:type_owner], argument_metadata[:type_name], argument_metadata[:type_subname])

elem_type_owner, elem_type_name, elem_type_package, elem_length, elem_precision, elem_scale, elem_char_used, index_by = r

if index_by == "VARCHAR2"
raise ArgumentError, "Index-by Varchar-Table (associative array) #{argument_metadata[:type_name]} is not supported"
end

element_metadata = {
position: 1,
data_type: if elem_type_owner == nil
elem_type_name
else
elem_type_package != nil ? "PL/SQL RECORD" : "OBJECT"
end,
in_out: argument_metadata[:in_out],
data_length: elem_length && elem_length.to_i,
data_precision: elem_precision && elem_precision.to_i,
data_scale: elem_scale && elem_scale.to_i,
char_used: elem_char_used,
char_length: elem_char_used && elem_length && elem_length.to_i,
type_owner: elem_type_owner,
type_name: elem_type_name,
type_subname: elem_type_package,
sql_type_name: elem_type_owner && build_sql_type_name(elem_type_owner, elem_type_package, elem_type_name),
type_object_type: elem_type_package != nil ? "PACKAGE" : nil,
defaulted: argument_metadata[:defaulted]
}
if elem_type_name.match('%ROWTYPE')
fields = {}
@schema.select_all(
"select column_id, column_name, data_type, data_length, data_precision, data_scale, char_used, char_length
FROM all_tab_columns
where owner = :owner and table_name = :table_name
order by column_id",
elem_type_owner, elem_type_name.sub('%ROWTYPE', '')) do |r|

rowtype_column_id, rowtype_column_name, rowtype_data_type, rowtype_data_length, rowtype_data_precision, rowtype_data_scale, rowtype_char_used, rowtype_char_length = r

fields[rowtype_column_name.downcase.to_sym] = {
position: rowtype_column_id.to_i,
data_type: rowtype_data_type,
in_out: 'OUT',
data_length: rowtype_data_length && rowtype_data_length.to_i,
data_precision: rowtype_data_precision && rowtype_data_precision.to_i,
data_scale: rowtype_data_scale && rowtype_data_scale.to_i,
char_used: rowtype_char_used == nil ? "0" : rowtype_char_used,
char_length: rowtype_char_used && rowtype_char_length && rowtype_char_length.to_i,
type_owner: nil,
type_name: nil,
type_subname: nil,
sql_type_name: nil,
defaulted: 'N'
}
end
element_metadata = {
position: 1,
data_type: "PL/SQL RECORD",
in_out: argument_metadata[:in_out],
data_length: elem_length && elem_length.to_i,
data_precision: elem_precision && elem_precision.to_i,
data_scale: elem_scale && elem_scale.to_i,
char_used: elem_char_used,
char_length: elem_char_used && elem_length && elem_length.to_i,
type_owner: elem_type_owner,
type_name: elem_type_name,
type_subname: elem_type_package,
sql_type_name: elem_type_owner && build_sql_type_name(elem_type_owner, elem_type_package, elem_type_name),
type_object_type: elem_type_package != nil ? "PACKAGE" : nil,
defaulted: argument_metadata[:defaulted],
fields: fields
}
else
element_metadata = {
position: 1,
data_type: if elem_type_owner == nil
elem_type_name
elsif elem_type_package != nil
"PL/SQL RECORD"
else
"OBJECT"
end,
in_out: argument_metadata[:in_out],
data_length: elem_length && elem_length.to_i,
data_precision: elem_precision && elem_precision.to_i,
data_scale: elem_scale && elem_scale.to_i,
char_used: elem_char_used,
char_length: elem_char_used && elem_length && elem_length.to_i,
type_owner: elem_type_owner,
type_name: elem_type_name,
type_subname: elem_type_package,
sql_type_name: elem_type_owner && build_sql_type_name(elem_type_owner, elem_type_package, elem_type_name),
type_object_type: elem_type_package != nil ? "PACKAGE" : nil,
defaulted: argument_metadata[:defaulted]
}
end

if elem_type_package != nil
element_metadata[:fields] = get_field_definitions(element_metadata)
end
when "TYPE"
r = @schema.select_first(
"SELECT elem_type_owner, elem_type_name, length, precision, scale, char_used
FROM ALL_COLL_TYPES t
WHERE t.owner = :owner AND t.TYPE_NAME = :type_name",
@schema_name, argument_metadata[:type_name]
"SELECT nvl(s.table_owner, t.elem_type_owner), nvl(s.table_name, t.elem_type_name), t.length, t.precision, t.scale, t.char_used
FROM all_coll_types t
LEFT JOIN all_synonyms s ON t.elem_type_owner = s.owner AND t.elem_type_name = s.synonym_name
WHERE t.owner = :owner
AND t.type_name = :type_name",
argument_metadata[:type_owner], argument_metadata[:type_name]
)
elem_type_owner, elem_type_name, elem_length, elem_precision, elem_scale, elem_char_used = r

2 changes: 1 addition & 1 deletion lib/plsql/variable.rb
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ def find(schema, variable, package, override_schema_name = nil)
AND type = 'PACKAGE'
AND UPPER(text) LIKE :variable_name",
override_schema_name || schema.schema_name, package, "%#{variable_upcase}%").each do |row|
if row[0] =~ /^\s*#{variable_upcase}\s+(CONSTANT\s+)?([A-Z0-9_. %]+(\([\w\s,]+\))?)\s*(NOT\s+NULL)?\s*((:=|DEFAULT).*)?;\s*(--.*)?$/i
if row[0] =~ /^\s*#{variable_upcase}\s+(CONSTANT\s+)?([A-Z0-9_. %]+(\([\w\s,]+\))?)\s*(NOT\s+NULL)?\s*((:=|DEFAULT).*)?;?\s*(--.*)?$/i
return new(schema, variable, package, $2.strip, override_schema_name)
end
end