Skip to content

Commit 4c33681

Browse files
authored
Merge pull request #196 from bpom/support_oracle_18c
Support oracle 18c
2 parents beca6d3 + b39a8ac commit 4c33681

File tree

2 files changed

+284
-4
lines changed

2 files changed

+284
-4
lines changed

lib/plsql/procedure.rb

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ def self.type_to_sql(metadata) #:nodoc:
8383

8484
# get procedure argument metadata from data dictionary
8585
def get_argument_metadata #:nodoc:
86+
if (@schema.connection.database_version <=> [18, 0, 0, 0]) >= 0
87+
get_argument_metadata_from_18c
88+
else
89+
get_argument_metadata_below_18c
90+
end
91+
end
92+
93+
def get_argument_metadata_below_18c #:nodoc:
8694
@arguments = {}
8795
@argument_list = {}
8896
@out_list = {}
@@ -197,6 +205,99 @@ def get_argument_metadata #:nodoc:
197205
construct_argument_list_for_overloads
198206
end
199207

208+
# get procedure argument metadata from data dictionary
209+
def get_argument_metadata_from_18c #:nodoc:
210+
@arguments = {}
211+
@argument_list = {}
212+
@out_list = {}
213+
@return = {}
214+
@overloaded = false
215+
216+
# store tmp tables for each overload for table parameters with types defined inside packages
217+
@tmp_table_names = {}
218+
# store if tmp tables are created for specific overload
219+
@tmp_tables_created = {}
220+
221+
@schema.select_all(
222+
"SELECT subprogram_id, object_name, TO_NUMBER(overload), argument_name, position,
223+
data_type, in_out, data_length, data_precision, data_scale, char_used,
224+
char_length, type_owner, nvl(type_subname, type_name),
225+
decode(type_object_type, 'PACKAGE', type_name, null), type_object_type, defaulted
226+
FROM all_arguments
227+
WHERE object_id = :object_id
228+
AND owner = :owner
229+
AND object_name = :procedure_name
230+
ORDER BY overload, sequence",
231+
@object_id, @schema_name, @procedure
232+
) do |r|
233+
234+
subprogram_id, object_name, overload, argument_name, position,
235+
data_type, in_out, data_length, data_precision, data_scale, char_used,
236+
char_length, type_owner, type_name, type_package, type_object_type, defaulted = r
237+
238+
@overloaded ||= !overload.nil?
239+
# if not overloaded then store arguments at key 0
240+
overload ||= 0
241+
@arguments[overload] ||= {}
242+
@return[overload] ||= nil
243+
@tmp_table_names[overload] ||= []
244+
245+
sql_type_name = build_sql_type_name(type_owner, type_package, type_name)
246+
247+
tmp_table_name = nil
248+
# type defined inside package
249+
if type_package
250+
if collection_type?(data_type)
251+
tmp_table_name = "#{Connection::RUBY_TEMP_TABLE_PREFIX}#{@schema.connection.session_id}_#{@object_id}_#{subprogram_id}_#{position}"
252+
end
253+
end
254+
255+
argument_metadata = {
256+
position: position && position.to_i,
257+
data_type: data_type,
258+
in_out: in_out,
259+
data_length: data_length && data_length.to_i,
260+
data_precision: data_precision && data_precision.to_i,
261+
data_scale: data_scale && data_scale.to_i,
262+
char_used: char_used,
263+
char_length: char_length && char_length.to_i,
264+
type_owner: type_owner,
265+
type_name: type_name,
266+
# TODO: should be renamed to type_package, when support for legacy database versions is dropped
267+
# due to the explicit change declaration of types in oracle plsql_type-catalogs (type_package + type_name),
268+
# the assignment of type + subtype was switched here for 18c and beyond
269+
type_subname: type_package,
270+
sql_type_name: sql_type_name,
271+
defaulted: defaulted,
272+
type_object_type: type_object_type
273+
}
274+
if tmp_table_name
275+
@tmp_table_names[overload] << [(argument_metadata[:tmp_table_name] = tmp_table_name), argument_metadata]
276+
end
277+
278+
if composite_type?(data_type)
279+
case data_type
280+
when "PL/SQL RECORD", "REF CURSOR"
281+
argument_metadata[:fields] = get_field_definitions(argument_metadata)
282+
when "PL/SQL TABLE", "TABLE", "VARRAY"
283+
argument_metadata[:element] = get_element_definition(argument_metadata)
284+
end
285+
end
286+
287+
# if function has return value
288+
if argument_name.nil? && in_out == "OUT"
289+
@return[overload] = argument_metadata
290+
else
291+
# sometime there are empty IN arguments in all_arguments view for procedures without arguments (e.g. for DBMS_OUTPUT.DISABLE)
292+
@arguments[overload][argument_name.downcase.to_sym] = argument_metadata if argument_name
293+
end
294+
end
295+
# if procedure is without arguments then create default empty argument list for default overload
296+
@arguments[0] = {} if @arguments.keys.empty?
297+
298+
construct_argument_list_for_overloads
299+
end
300+
200301
def construct_argument_list_for_overloads #:nodoc:
201302
@overloads = @arguments.keys.sort
202303
@overloads.each do |overload|
@@ -229,6 +330,185 @@ def ensure_tmp_tables_created(overload) #:nodoc:
229330
@tmp_tables_created[overload] = true
230331
end
231332

333+
def build_sql_type_name(type_owner, type_package, type_name) #:nodoc:
334+
if type_owner == nil || type_owner == "PUBLIC"
335+
type_owner_res = ""
336+
else
337+
type_owner_res = "#{type_owner}."
338+
end
339+
340+
if type_package == nil
341+
type_name_res = type_name
342+
else
343+
type_name_res = "#{type_package}.#{type_name}"
344+
end
345+
type_name_res && "#{type_owner_res}#{type_name_res}"
346+
end
347+
348+
def get_field_definitions(argument_metadata) #:nodoc:
349+
fields = {}
350+
case argument_metadata[:type_object_type]
351+
when "PACKAGE"
352+
@schema.select_all(
353+
"SELECT attr_no, attr_name, attr_type_owner, attr_type_name, attr_type_package, length, precision, scale, char_used
354+
FROM ALL_PLSQL_TYPES t, ALL_PLSQL_TYPE_ATTRS ta
355+
WHERE t.OWNER = :owner AND t.type_name = :type_name AND t.package_name = :package_name
356+
AND ta.OWNER = t.owner AND ta.TYPE_NAME = t.TYPE_NAME AND ta.PACKAGE_NAME = t.PACKAGE_NAME
357+
ORDER BY attr_no",
358+
@schema_name, argument_metadata[:type_name], argument_metadata[:type_subname]) do |r|
359+
360+
attr_no, attr_name, attr_type_owner, attr_type_name, attr_type_package, attr_length, attr_precision, attr_scale, attr_char_used = r
361+
362+
fields[attr_name.downcase.to_sym] = {
363+
position: attr_no.to_i,
364+
data_type: attr_type_owner == nil ? attr_type_name : get_composite_type(attr_type_owner, attr_type_name, attr_type_package),
365+
in_out: argument_metadata[:in_out],
366+
data_length: attr_length && attr_length.to_i,
367+
data_precision: attr_precision && attr_precision.to_i,
368+
data_scale: attr_scale && attr_scale.to_i,
369+
char_used: attr_char_used == nil ? "0" : attr_char_used,
370+
char_length: attr_char_used && attr_length && attr_length.to_i,
371+
type_owner: attr_type_owner,
372+
type_name: attr_type_owner && attr_type_name,
373+
type_subname: attr_type_package,
374+
sql_type_name: attr_type_owner && build_sql_type_name(attr_type_owner, attr_type_package, attr_type_name),
375+
defaulted: argument_metadata[:defaulted]
376+
}
377+
378+
if fields[attr_name.downcase.to_sym][:data_type] == "TABLE" && fields[attr_name.downcase.to_sym][:type_subname] != nil
379+
fields[attr_name.downcase.to_sym][:fields] = get_field_definitions(fields[attr_name.downcase.to_sym])
380+
end
381+
end
382+
when "TABLE", "VIEW"
383+
@schema.select_all(
384+
"SELECT column_id, column_name, data_type, data_length, data_precision, data_scale, char_length, char_used
385+
FROM ALL_TAB_COLS WHERE OWNER = :owner AND TABLE_NAME = :type_name
386+
ORDER BY column_id",
387+
@schema_name, argument_metadata[:type_name]) do |r|
388+
389+
col_no, col_name, col_type_name, col_length, col_precision, col_scale, col_char_length, col_char_used = r
390+
391+
fields[col_name.downcase.to_sym] = {
392+
position: col_no.to_i,
393+
data_type: col_type_name,
394+
in_out: argument_metadata[:in_out],
395+
data_length: col_length && col_length.to_i,
396+
data_precision: col_precision && col_precision.to_i,
397+
data_scale: col_scale && col_scale.to_i,
398+
char_used: col_char_used == nil ? "0" : col_char_used,
399+
char_length: col_char_length && col_char_length.to_i,
400+
type_owner: nil,
401+
type_name: nil,
402+
type_subname: nil,
403+
sql_type_name: nil,
404+
defaulted: argument_metadata[:defaulted]
405+
}
406+
end
407+
end
408+
fields
409+
end
410+
411+
def get_element_definition(argument_metadata) #:nodoc:
412+
element_metadata = {}
413+
if collection_type?(argument_metadata[:data_type])
414+
case argument_metadata[:type_object_type]
415+
when "PACKAGE"
416+
r = @schema.select_first(
417+
"SELECT elem_type_owner, elem_type_name, elem_type_package, length, precision, scale, char_used, index_by
418+
FROM ALL_PLSQL_COLL_TYPES t
419+
WHERE t.OWNER = :owner AND t.TYPE_NAME = :type_name AND t.PACKAGE_NAME = :package_name",
420+
@schema_name, argument_metadata[:type_name], argument_metadata[:type_subname])
421+
422+
elem_type_owner, elem_type_name, elem_type_package, elem_length, elem_precision, elem_scale, elem_char_used, index_by = r
423+
424+
if index_by == "VARCHAR2"
425+
raise ArgumentError, "Index-by Varchar-Table (associative array) #{argument_metadata[:type_name]} is not supported"
426+
end
427+
428+
element_metadata = {
429+
position: 1,
430+
data_type: if elem_type_owner == nil
431+
elem_type_name
432+
else
433+
elem_type_package != nil ? "PL/SQL RECORD" : "OBJECT"
434+
end,
435+
in_out: argument_metadata[:in_out],
436+
data_length: elem_length && elem_length.to_i,
437+
data_precision: elem_precision && elem_precision.to_i,
438+
data_scale: elem_scale && elem_scale.to_i,
439+
char_used: elem_char_used,
440+
char_length: elem_char_used && elem_length && elem_length.to_i,
441+
type_owner: elem_type_owner,
442+
type_name: elem_type_name,
443+
type_subname: elem_type_package,
444+
sql_type_name: elem_type_owner && build_sql_type_name(elem_type_owner, elem_type_package, elem_type_name),
445+
type_object_type: elem_type_package != nil ? "PACKAGE" : nil,
446+
defaulted: argument_metadata[:defaulted]
447+
}
448+
449+
if elem_type_package != nil
450+
element_metadata[:fields] = get_field_definitions(element_metadata)
451+
end
452+
when "TYPE"
453+
r = @schema.select_first(
454+
"SELECT elem_type_owner, elem_type_name, length, precision, scale, char_used
455+
FROM ALL_COLL_TYPES t
456+
WHERE t.owner = :owner AND t.TYPE_NAME = :type_name",
457+
@schema_name, argument_metadata[:type_name]
458+
)
459+
elem_type_owner, elem_type_name, elem_length, elem_precision, elem_scale, elem_char_used = r
460+
461+
element_metadata = {
462+
position: 1,
463+
data_type: elem_type_owner == nil ? elem_type_name : "OBJECT",
464+
in_out: argument_metadata[:in_out],
465+
data_length: elem_length && elem_length.to_i,
466+
data_precision: elem_precision && elem_precision.to_i,
467+
data_scale: elem_scale && elem_scale.to_i,
468+
char_used: elem_char_used,
469+
char_length: elem_char_used && elem_length && elem_length.to_i,
470+
type_owner: elem_type_owner,
471+
type_name: elem_type_name,
472+
type_subname: nil,
473+
sql_type_name: elem_type_owner && build_sql_type_name(elem_type_owner, nil, elem_type_name),
474+
defaulted: argument_metadata[:defaulted]
475+
}
476+
end
477+
else
478+
element_metadata = {
479+
position: 1,
480+
data_type: "PL/SQL RECORD",
481+
in_out: argument_metadata[:in_out],
482+
data_length: nil,
483+
data_precision: nil,
484+
data_scale: nil,
485+
char_used: "B",
486+
char_length: 0,
487+
type_owner: argument_metadata[:type_owner],
488+
type_name: argument_metadata[:type_name],
489+
type_subname: argument_metadata[:type_subname],
490+
sql_type_name: build_sql_type_name(argument_metadata[:type_owner], argument_metadata[:type_subname], argument_metadata[:type_name]),
491+
defaulted: argument_metadata[:defaulted]
492+
}
493+
494+
if element_metadata[:type_subname] != nil
495+
element_metadata[:fields] = get_field_definitions(element_metadata)
496+
end
497+
end
498+
element_metadata
499+
end
500+
501+
def get_composite_type(type_owner, type_name, type_package)
502+
r = @schema.select_first("SELECT typecode FROM all_plsql_types WHERE owner = :owner AND type_name = :type_name AND package_name = :type_package
503+
UNION ALL
504+
SELECT typecode FROM all_types WHERE owner = :owner AND type_name = :type_name",
505+
type_owner, type_name, type_package, type_owner, type_name)
506+
typecode = r[0]
507+
raise ArgumentError, "#{type_name} type #{build_sql_type_name(type_owner, type_package, type_name)} definition inside package is not supported as part of other type definition," <<
508+
" use CREATE TYPE outside package" if typecode == "COLLECTION"
509+
typecode
510+
end
511+
232512
PLSQL_COMPOSITE_TYPES = ["PL/SQL RECORD", "PL/SQL TABLE", "TABLE", "VARRAY", "REF CURSOR"].freeze
233513
def composite_type?(data_type) #:nodoc:
234514
PLSQL_COMPOSITE_TYPES.include? data_type

spec/plsql/procedure_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@
687687
is_approved BOOLEAN
688688
);
689689
690-
TYPE table_of_records IS TABLE OF t_employee;
690+
TYPE table_of_records IS TABLE OF test_record.t_employee;
691691
692692
FUNCTION test_full_name(p_employee t_employee)
693693
RETURN VARCHAR2;
@@ -1101,7 +1101,7 @@ def new_candidate(status)
11011101
last_name VARCHAR(50),
11021102
hire_date DATE
11031103
);
1104-
TYPE t_employees IS TABLE OF t_employee;
1104+
TYPE t_employees IS TABLE OF test_collections.t_employee;
11051105
FUNCTION test_employees (p_employees IN OUT t_employees)
11061106
RETURN t_employees;
11071107
-- these types with tables in lower level are not yet supported
@@ -1110,7 +1110,7 @@ def new_candidate(status)
11101110
first_name VARCHAR2(50),
11111111
last_name VARCHAR(50),
11121112
hire_date DATE,
1113-
numbers t_numbers
1113+
numbers test_collections.t_numbers
11141114
);
11151115
FUNCTION test_employee2 (p_employee IN OUT t_employee2)
11161116
RETURN t_employee2;
@@ -1366,7 +1366,7 @@ def new_candidate(status)
13661366
last_name VARCHAR(50),
13671367
hire_date DATE
13681368
);
1369-
TYPE t_employees IS TABLE OF t_employee
1369+
TYPE t_employees IS TABLE OF test_collections.t_employee
13701370
INDEX BY BINARY_INTEGER;
13711371
FUNCTION test_employees (p_employees IN OUT t_employees)
13721372
RETURN t_employees;

0 commit comments

Comments
 (0)