ruby-plsql 0.9.9-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,626 @@
1
+ module PLSQL
2
+ class ProcedureCall # :nodoc:
3
+ def initialize(procedure, args = [], options = {})
4
+ @procedure = procedure
5
+ @schema = @procedure.schema
6
+ @dbms_output_stream = @schema.dbms_output_stream
7
+ @skip_self = options[:skip_self]
8
+ @self = options[:self]
9
+
10
+ if args.size == 1 && args[0].is_a?(Hash) && args[0].keys.all? { |k| k.is_a?(Symbol) }
11
+ args[0] = args[0].map { |k, v| [k.downcase, v] }.to_h
12
+ end
13
+
14
+ @overload = get_overload_from_arguments_list(args)
15
+ @procedure.ensure_tmp_tables_created(@overload) if @procedure.respond_to?(:ensure_tmp_tables_created)
16
+ construct_sql(args)
17
+ end
18
+
19
+ def exec
20
+ # puts "DEBUG: sql = #{@sql.gsub("\n","<br/>\n")}"
21
+ @cursor = @schema.connection.parse(@sql)
22
+
23
+ @bind_values.each do |arg, value|
24
+ @cursor.bind_param(":#{arg}", value, @bind_metadata[arg])
25
+ end
26
+
27
+ @return_vars.each do |var|
28
+ @cursor.bind_param(":#{var}", nil, @return_vars_metadata[var])
29
+ end
30
+
31
+ @cursor.exec
32
+
33
+ if block_given?
34
+ yield get_return_value
35
+ nil
36
+ else
37
+ get_return_value
38
+ end
39
+ ensure
40
+ @cursor.close if @cursor
41
+ dbms_output_log
42
+ end
43
+
44
+ private
45
+
46
+ def get_overload_from_arguments_list(args)
47
+ # if not overloaded then overload index 0 is used
48
+ return 0 unless @procedure.overloaded?
49
+ # If named arguments are used then
50
+ # there should be just one Hash argument with symbol keys
51
+ if args.size == 1 && args[0].is_a?(Hash) && args[0].keys.all? { |k| k.is_a?(Symbol) }
52
+ args_keys = args[0].keys
53
+ # implicit SELF argument for object instance procedures
54
+ args_keys << :self if @self && !args_keys.include?(:self)
55
+ number_of_args = args_keys.size
56
+ matching_overloads = [] # overloads with exact or smaller number of matching named arguments
57
+ overload_argument_list.keys.each do |ov|
58
+ # assume that missing arguments have default value
59
+ missing_arguments_count = overload_argument_list[ov].size - number_of_args
60
+ if missing_arguments_count >= 0 &&
61
+ args_keys.all? { |k| overload_argument_list[ov].include?(k) }
62
+ matching_overloads << [ov, missing_arguments_count]
63
+ end
64
+ end
65
+ # pick first matching overload with smallest missing arguments count
66
+ # (hoping that missing arguments will be defaulted - cannot find default value from all_arguments)
67
+ overload = matching_overloads.sort_by { |ov, score| score }[0][0]
68
+ # otherwise try matching by sequential arguments count and types
69
+ else
70
+ number_of_args = args.size
71
+ matching_types = []
72
+ # if implicit SELF argument for object instance procedures should be passed
73
+ # then it should be added as first argument to find matches
74
+ if @self
75
+ number_of_args += 1
76
+ matching_types << ["OBJECT"]
77
+ end
78
+ args.each do |arg|
79
+ matching_types << matching_oracle_types_for_ruby_value(arg)
80
+ end
81
+ exact_overloads = [] # overloads with exact number of matching arguments
82
+ smaller_overloads = [] # overloads with smaller number of matching arguments
83
+ # overload = overload_argument_list.keys.detect do |ov|
84
+ # overload_argument_list[ov].size == number_of_args
85
+ # end
86
+ overload_argument_list.keys.each do |ov|
87
+ score = 0 # lower score is better match
88
+ ov_arg_list_size = overload_argument_list[ov].size
89
+ if (number_of_args <= ov_arg_list_size &&
90
+ (0..(number_of_args - 1)).all? do |i|
91
+ ov_arg = overload_argument_list[ov][i]
92
+ matching_types[i] == :all || # either value matches any type
93
+ (ind = matching_types[i].index(overload_arguments[ov][ov_arg][:data_type])) &&
94
+ (score += ind) # or add index of matched type
95
+ end)
96
+ if number_of_args == ov_arg_list_size
97
+ exact_overloads << [ov, score]
98
+ else
99
+ smaller_overloads << [ov, score]
100
+ end
101
+ end
102
+ end
103
+ # pick either first exact matching overload of first matching with smaller argument count
104
+ # (hoping that missing arguments will be defaulted - cannot find default value from all_arguments)
105
+ overload = if !exact_overloads.empty?
106
+ exact_overloads.sort_by { |ov, score| score }[0][0]
107
+ elsif !smaller_overloads.empty?
108
+ smaller_overloads.sort_by { |ov, score| score }[0][0]
109
+ end
110
+ end
111
+ raise ArgumentError, "Wrong number or types of arguments passed to overloaded PL/SQL procedure" unless overload
112
+ overload
113
+ end
114
+
115
+ MATCHING_TYPES = {
116
+ integer: ["NUMBER", "NATURAL", "NATURALN", "POSITIVE", "POSITIVEN", "SIGNTYPE", "SIMPLE_INTEGER", "PLS_INTEGER", "BINARY_INTEGER"],
117
+ decimal: ["NUMBER", "BINARY_FLOAT", "BINARY_DOUBLE"],
118
+ string: ["VARCHAR", "VARCHAR2", "NVARCHAR2", "CHAR", "NCHAR", "CLOB", "BLOB", "XMLTYPE", "OPAQUE/XMLTYPE"],
119
+ date: ["DATE"],
120
+ time: ["DATE", "TIMESTAMP", "TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH LOCAL TIME ZONE"],
121
+ boolean: ["PL/SQL BOOLEAN"],
122
+ hash: ["PL/SQL RECORD", "OBJECT", "PL/SQL TABLE"],
123
+ array: ["TABLE", "VARRAY"],
124
+ cursor: ["REF CURSOR"]
125
+ }
126
+ def matching_oracle_types_for_ruby_value(value)
127
+ case value
128
+ when NilClass
129
+ :all
130
+ when Integer
131
+ MATCHING_TYPES[:integer]
132
+ when BigDecimal, Float
133
+ MATCHING_TYPES[:decimal]
134
+ when String
135
+ MATCHING_TYPES[:string]
136
+ when Date
137
+ MATCHING_TYPES[:date]
138
+ when Time
139
+ MATCHING_TYPES[:time]
140
+ when TrueClass, FalseClass
141
+ MATCHING_TYPES[:boolean]
142
+ when Hash
143
+ MATCHING_TYPES[:hash]
144
+ when Array
145
+ MATCHING_TYPES[:array]
146
+ when CursorCommon
147
+ MATCHING_TYPES[:cursor]
148
+ end
149
+ end
150
+
151
+ def construct_sql(args)
152
+ @declare_sql = +""
153
+ @assignment_sql = +""
154
+ @call_sql = +""
155
+ @return_sql = +""
156
+ @return_vars = []
157
+ @return_vars_metadata = {}
158
+
159
+ @call_sql << add_return if return_metadata
160
+ # construct procedure call if procedure name is available
161
+ # otherwise will get surrounding call_sql from @procedure (used for table statements)
162
+ if procedure_name
163
+ @call_sql << "#{schema_name}." if schema_name
164
+ @call_sql << "#{package_name}." if package_name
165
+ @call_sql << "#{procedure_name}("
166
+ end
167
+
168
+ @bind_values = {}
169
+ @bind_metadata = {}
170
+
171
+ # Named arguments
172
+ # there should be just one Hash argument with symbol keys
173
+ if args.size == 1 && args[0].is_a?(Hash) && args[0].keys.all? { |k| k.is_a?(Symbol) } &&
174
+ # do not use named arguments if procedure has just one PL/SQL record PL/SQL table or object type argument -
175
+ # in that case passed Hash should be used as value for this PL/SQL record argument
176
+ # (which will be processed in sequential arguments bracnh)
177
+ !(argument_list.size == 1 &&
178
+ ["PL/SQL RECORD", "PL/SQL TABLE", "OBJECT"].include?(arguments[(only_argument = argument_list[0])][:data_type]) &&
179
+ args[0].keys != [only_argument])
180
+ # Add missing output arguments with nil value
181
+ arguments.each do |arg, metadata|
182
+ if !args[0].has_key?(arg) && metadata[:in_out] == "OUT"
183
+ args[0][arg] = nil
184
+ end
185
+ end
186
+ # Add SELF argument if provided
187
+ args[0][:self] = @self if @self
188
+ # Add passed parameters to procedure call with parameter names
189
+ @call_sql << args[0].map do |arg, value|
190
+ "#{arg} => " << add_argument(arg, value)
191
+ end.join(", ")
192
+
193
+ # Sequential arguments
194
+ else
195
+ # add SELF as first argument if provided
196
+ args.unshift(@self) if @self
197
+ argument_count = argument_list.size
198
+ raise ArgumentError, "Too many arguments passed to PL/SQL procedure" if args.size > argument_count
199
+ # Add missing output arguments with nil value
200
+ if args.size < argument_count &&
201
+ (args.size...argument_count).all? { |i| arguments[argument_list[i]][:in_out] == "OUT" }
202
+ args += [nil] * (argument_count - args.size)
203
+ end
204
+ # Add passed parameters to procedure call in sequence
205
+ @call_sql << (0...args.size).map do |i|
206
+ arg = argument_list[i]
207
+ value = args[i]
208
+ add_argument(arg, value)
209
+ end.join(", ")
210
+ end
211
+
212
+ # finish procedure call construction if procedure name is available
213
+ # otherwise will get surrounding call_sql from @procedure (used for table statements)
214
+ if procedure_name
215
+ @call_sql << ");\n"
216
+ else
217
+ @call_sql = @procedure.call_sql(@call_sql)
218
+ end
219
+ add_out_variables
220
+
221
+ @sql = @declare_sql.empty? ? +"" : +"DECLARE\n" << @declare_sql
222
+ @sql << "BEGIN\n" << @assignment_sql << dbms_output_enable_sql << @call_sql << @return_sql << "END;\n"
223
+ end
224
+
225
+ def add_argument(argument, value, argument_metadata = nil)
226
+ argument_metadata ||= arguments[argument]
227
+ raise ArgumentError, "Wrong argument #{argument.inspect} passed to PL/SQL procedure" unless argument_metadata
228
+ case argument_metadata[:data_type]
229
+ when "PL/SQL RECORD"
230
+ add_record_declaration(argument, argument_metadata)
231
+ record_assignment_sql, record_bind_values, record_bind_metadata =
232
+ record_assignment_sql_values_metadata(argument, argument_metadata, value)
233
+ @assignment_sql << record_assignment_sql
234
+ @bind_values.merge!(record_bind_values)
235
+ @bind_metadata.merge!(record_bind_metadata)
236
+ "l_#{argument}"
237
+ when "PL/SQL BOOLEAN"
238
+ @declare_sql << "l_#{argument} BOOLEAN;\n"
239
+ @assignment_sql << "l_#{argument} := (:#{argument} = 1);\n"
240
+ @bind_values[argument] = value.nil? ? nil : (value ? 1 : 0)
241
+ @bind_metadata[argument] = argument_metadata.merge(data_type: "NUMBER", data_precision: 1)
242
+ "l_#{argument}"
243
+ when "UNDEFINED", "XMLTYPE", "OPAQUE/XMLTYPE"
244
+ if argument_metadata[:type_name] == "XMLTYPE" || argument_metadata[:data_type] =~ /XMLTYPE/
245
+ @declare_sql << "l_#{argument} XMLTYPE;\n"
246
+ @assignment_sql << "l_#{argument} := XMLTYPE(:#{argument});\n" if not value.nil?
247
+ @bind_values[argument] = value if not value.nil?
248
+ @bind_metadata[argument] = argument_metadata.merge(data_type: "CLOB")
249
+ "l_#{argument}"
250
+ end
251
+ else
252
+ # TABLE or PL/SQL TABLE type defined inside package
253
+ if argument_metadata[:tmp_table_name]
254
+ add_table_declaration_and_assignment(argument, argument_metadata)
255
+ insert_values_into_tmp_table(argument, argument_metadata, value)
256
+ "l_#{argument}"
257
+ else
258
+ @bind_values[argument] = value
259
+ @bind_metadata[argument] = argument_metadata
260
+ ":#{argument}"
261
+ end
262
+ end
263
+ end
264
+
265
+ def add_table_declaration_and_assignment(argument, argument_metadata)
266
+ is_index_by_table = argument_metadata[:data_type] == "PL/SQL TABLE"
267
+ @declare_sql << "l_#{argument} #{argument_metadata[:sql_type_name]}#{is_index_by_table ? nil : " := #{argument_metadata[:sql_type_name]}()"};\n"
268
+ @assignment_sql << "FOR r_#{argument} IN c_#{argument} LOOP\n"
269
+ @assignment_sql << "l_#{argument}.EXTEND;\n" unless is_index_by_table
270
+ case argument_metadata[:element][:data_type]
271
+ when "PL/SQL RECORD"
272
+ fields = record_fields_sorted_by_position(argument_metadata[:element][:fields])
273
+ fields_string = is_index_by_table ? "*" : fields.join(", ")
274
+ @declare_sql << "CURSOR c_#{argument} IS SELECT #{fields_string} FROM #{argument_metadata[:tmp_table_name]} ORDER BY i__;\n"
275
+ if is_index_by_table
276
+ fields.each do |field|
277
+ @assignment_sql << "l_#{argument}(r_#{argument}.i__).#{field} := r_#{argument}.#{field};\n"
278
+ end
279
+ else
280
+ @assignment_sql << "l_#{argument}(l_#{argument}.COUNT) := r_#{argument};\n"
281
+ end
282
+ else
283
+ @declare_sql << "CURSOR c_#{argument} IS SELECT * FROM #{argument_metadata[:tmp_table_name]} ORDER BY i__;\n"
284
+ @assignment_sql << "l_#{argument}(r_#{argument}.i__) := r_#{argument}.element;\n"
285
+ end
286
+ @assignment_sql << "END LOOP;\n"
287
+ @assignment_sql << "DELETE FROM #{argument_metadata[:tmp_table_name]};\n"
288
+ end
289
+
290
+ def insert_values_into_tmp_table(argument, argument_metadata, values)
291
+ return unless values && !values.empty?
292
+ is_index_by_table = argument_metadata[:data_type] == "PL/SQL TABLE"
293
+ if is_index_by_table
294
+ raise ArgumentError, "Hash value should be passed for #{argument.inspect} argument" unless values.is_a?(Hash)
295
+ else
296
+ raise ArgumentError, "Array value should be passed for #{argument.inspect} argument" unless values.is_a?(Array)
297
+ end
298
+ tmp_table = @schema.root_schema.send(argument_metadata[:tmp_table_name])
299
+ # insert values without autocommit
300
+ old_autocommit = @schema.connection.autocommit?
301
+ @schema.connection.autocommit = false if old_autocommit
302
+ tmp_table.delete
303
+ case argument_metadata[:element][:data_type]
304
+ when "PL/SQL RECORD"
305
+ values_with_index = []
306
+ if is_index_by_table
307
+ values.each { |i, v| values_with_index << v.merge(i__: i) }
308
+ else
309
+ values.each_with_index { |v, i| values_with_index << v.merge(i__: i + 1) }
310
+ end
311
+ tmp_table.insert values_with_index
312
+ else
313
+ values_with_index = []
314
+ if is_index_by_table
315
+ values.each { |i, v| values_with_index << [v, i] }
316
+ else
317
+ values.each_with_index { |v, i| values_with_index << [v, i + 1] }
318
+ end
319
+ tmp_table.insert_values [:element, :i__], *values_with_index
320
+ end
321
+ @schema.connection.autocommit = true if old_autocommit
322
+ end
323
+
324
+ def add_record_declaration(argument, argument_metadata)
325
+ @declare_sql << if argument_metadata[:type_subname]
326
+ "l_#{argument} #{argument_metadata[:sql_type_name]};\n"
327
+ else
328
+ fields_metadata = argument_metadata[:fields]
329
+ sql = +"TYPE t_#{argument} IS RECORD (\n"
330
+ sql << record_fields_sorted_by_position(fields_metadata).map do |field|
331
+ metadata = fields_metadata[field]
332
+ "#{field} #{type_to_sql(metadata)}"
333
+ end.join(",\n")
334
+ sql << ");\n"
335
+ sql << "l_#{argument} t_#{argument};\n"
336
+ end
337
+ end
338
+
339
+ def record_fields_sorted_by_position(fields_metadata)
340
+ fields_metadata.keys.sort_by { |k| fields_metadata[k][:position] }
341
+ end
342
+
343
+ def record_assignment_sql_values_metadata(argument, argument_metadata, record_value)
344
+ sql = +""
345
+ bind_values = {}
346
+ bind_metadata = {}
347
+ (record_value || {}).each do |key, value|
348
+ field = key.is_a?(Symbol) ? key : key.to_s.downcase.to_sym
349
+ metadata = argument_metadata[:fields][field]
350
+ raise ArgumentError, "Wrong field name #{key.inspect} passed to PL/SQL record argument #{argument.inspect}" unless metadata
351
+ bind_variable = :"#{argument}_f#{metadata[:position]}"
352
+ case metadata[:data_type]
353
+ when "PL/SQL BOOLEAN"
354
+ sql << "l_#{argument}.#{field} := (:#{bind_variable} = 1);\n"
355
+ bind_values[bind_variable] = value.nil? ? nil : (value ? 1 : 0)
356
+ bind_metadata[bind_variable] = metadata.merge(data_type: "NUMBER", data_precision: 1)
357
+ else
358
+ sql << "l_#{argument}.#{field} := :#{bind_variable};\n"
359
+ bind_values[bind_variable] = value
360
+ bind_metadata[bind_variable] = metadata
361
+ end
362
+ end
363
+ [sql, bind_values, bind_metadata]
364
+ end
365
+
366
+ def add_return
367
+ add_return_variable(:return, return_metadata, true)
368
+ end
369
+
370
+ def add_out_variables
371
+ out_list.each do |argument|
372
+ add_return_variable(argument, arguments[argument])
373
+ end
374
+ end
375
+
376
+ def add_return_variable(argument, argument_metadata, is_return_value = false)
377
+ case argument_metadata[:data_type]
378
+ when "PL/SQL RECORD"
379
+ add_record_declaration(argument, argument_metadata) if is_return_value
380
+ argument_metadata[:fields].each do |field, metadata|
381
+ # should use different output bind variable as JDBC does not support
382
+ # if output bind variable appears in several places
383
+ bind_variable = :"#{argument}_o#{metadata[:position]}"
384
+ case metadata[:data_type]
385
+ when "PL/SQL BOOLEAN"
386
+ @return_vars << bind_variable
387
+ @return_vars_metadata[bind_variable] = metadata.merge(data_type: "NUMBER", data_precision: 1)
388
+ arg_field = "l_#{argument}.#{field}"
389
+ @return_sql << ":#{bind_variable} := " << "CASE WHEN #{arg_field} = true THEN 1 " <<
390
+ "WHEN #{arg_field} = false THEN 0 ELSE NULL END;\n"
391
+ else
392
+ @return_vars << bind_variable
393
+ @return_vars_metadata[bind_variable] = metadata
394
+ @return_sql << ":#{bind_variable} := l_#{argument}.#{field};\n"
395
+ end
396
+ end
397
+ "l_#{argument} := " if is_return_value
398
+ when "UNDEFINED", "XMLTYPE", "OPAQUE/XMLTYPE"
399
+ if argument_metadata[:type_name] == "XMLTYPE" || argument_metadata[:data_type] =~ /XMLTYPE/
400
+ @declare_sql << "l_#{argument} XMLTYPE;\n" if is_return_value
401
+ bind_variable = :"o_#{argument}"
402
+ @return_vars << bind_variable
403
+ @return_vars_metadata[bind_variable] = argument_metadata.merge(data_type: "CLOB")
404
+ @return_sql << ":#{bind_variable} := CASE WHEN l_#{argument} IS NOT NULL THEN l_#{argument}.getclobval() END;\n"
405
+ "l_#{argument} := " if is_return_value
406
+ end
407
+ when "PL/SQL BOOLEAN"
408
+ @declare_sql << "l_#{argument} BOOLEAN;\n" if is_return_value
409
+ @declare_sql << "o_#{argument} NUMBER(1);\n"
410
+ # should use different output bind variable as JDBC does not support
411
+ # if output bind variable appears in several places
412
+ bind_variable = :"o_#{argument}"
413
+ @return_vars << bind_variable
414
+ @return_vars_metadata[bind_variable] = argument_metadata.merge(data_type: "NUMBER", data_precision: 1)
415
+ @return_sql << "IF l_#{argument} IS NULL THEN\no_#{argument} := NULL;\n" <<
416
+ "ELSIF l_#{argument} THEN\no_#{argument} := 1;\nELSE\no_#{argument} := 0;\nEND IF;\n" <<
417
+ ":#{bind_variable} := o_#{argument};\n"
418
+ "l_#{argument} := " if is_return_value
419
+ else
420
+ if argument_metadata[:tmp_table_name]
421
+ add_return_table(argument, argument_metadata, is_return_value)
422
+ elsif is_return_value
423
+ @return_vars << argument
424
+ @return_vars_metadata[argument] = argument_metadata
425
+ ":#{argument} := "
426
+ end
427
+ end
428
+ end
429
+
430
+ def add_return_table(argument, argument_metadata, is_return_value = false)
431
+ is_index_by_table = argument_metadata[:data_type] == "PL/SQL TABLE"
432
+ declare_i__
433
+ @declare_sql << "l_return #{return_metadata[:sql_type_name]};\n" if is_return_value
434
+ @return_vars << argument
435
+ @return_vars_metadata[argument] = argument_metadata.merge(data_type: "REF CURSOR")
436
+ @return_sql << if is_index_by_table
437
+ "i__ := l_#{argument}.FIRST;\nLOOP\nEXIT WHEN i__ IS NULL;\n"
438
+ else
439
+ "IF l_#{argument}.COUNT > 0 THEN\nFOR i__ IN l_#{argument}.FIRST..l_#{argument}.LAST LOOP\n"
440
+ end
441
+ case argument_metadata[:element][:data_type]
442
+ when "PL/SQL RECORD"
443
+ field_names = record_fields_sorted_by_position(argument_metadata[:element][:fields])
444
+ values_string = field_names.map { |f| "l_#{argument}(i__).#{f}" }.join(", ")
445
+ @return_sql << "INSERT INTO #{argument_metadata[:tmp_table_name]} VALUES (#{values_string}, i__);\n"
446
+ return_fields_string = is_index_by_table ? "*" : field_names.join(", ")
447
+ else
448
+ @return_sql << "INSERT INTO #{argument_metadata[:tmp_table_name]} VALUES (l_#{argument}(i__), i__);\n"
449
+ return_fields_string = "*"
450
+ end
451
+ @return_sql << "i__ := l_#{argument}.NEXT(i__);\n" if is_index_by_table
452
+ @return_sql << "END LOOP;\n"
453
+ @return_sql << "END IF;\n" unless is_index_by_table
454
+ @return_sql << "OPEN :#{argument} FOR SELECT #{return_fields_string} FROM #{argument_metadata[:tmp_table_name]} ORDER BY i__;\n"
455
+ @return_sql << "DELETE FROM #{argument_metadata[:tmp_table_name]};\n"
456
+ "l_#{argument} := " if is_return_value
457
+ end
458
+
459
+ # declare once temp variable i__ that is used as itertor
460
+ def declare_i__
461
+ unless @declared_i__
462
+ @declare_sql << "i__ PLS_INTEGER;\n"
463
+ @declared_i__ = true
464
+ end
465
+ end
466
+
467
+ def type_to_sql(metadata)
468
+ ProcedureCommon.type_to_sql(metadata)
469
+ end
470
+
471
+ def get_return_value
472
+ # if function with output parameters
473
+ if return_metadata && out_list.size > 0
474
+ result = [function_return_value, {}]
475
+ out_list.each do |k|
476
+ result[1][k] = out_variable_value(k)
477
+ end
478
+ result
479
+ # if function without output parameters
480
+ elsif return_metadata
481
+ function_return_value
482
+ # if procedure with output parameters
483
+ elsif out_list.size > 0
484
+ result = {}
485
+ out_list.each do |k|
486
+ result[k] = out_variable_value(k)
487
+ end
488
+ result
489
+ # if procedure without output parameters
490
+ else
491
+ nil
492
+ end
493
+ end
494
+
495
+ def function_return_value
496
+ return_variable_value(:return, return_metadata)
497
+ end
498
+
499
+ def out_variable_value(argument)
500
+ return_variable_value(argument, arguments[argument])
501
+ end
502
+
503
+ def return_variable_value(argument, argument_metadata)
504
+ case argument_metadata[:data_type]
505
+ when "PL/SQL RECORD"
506
+ return_value = {}
507
+ argument_metadata[:fields].each do |field, metadata|
508
+ field_value = @cursor[":#{argument}_o#{metadata[:position]}"]
509
+ case metadata[:data_type]
510
+ when "PL/SQL BOOLEAN"
511
+ return_value[field] = field_value.nil? ? nil : field_value == 1
512
+ else
513
+ return_value[field] = field_value
514
+ end
515
+ end
516
+ return_value
517
+ when "PL/SQL BOOLEAN"
518
+ numeric_value = @cursor[":o_#{argument}"]
519
+ numeric_value.nil? ? nil : numeric_value == 1
520
+ when "UNDEFINED", "XMLTYPE", "OPAQUE/XMLTYPE"
521
+ if argument_metadata[:type_name] == "XMLTYPE" || argument_metadata[:data_type] =~ /XMLTYPE/
522
+ @cursor[":o_#{argument}"]
523
+ end
524
+ else
525
+ if argument_metadata[:tmp_table_name]
526
+ is_index_by_table = argument_metadata[:data_type] == "PL/SQL TABLE"
527
+ case argument_metadata[:element][:data_type]
528
+ when "PL/SQL RECORD"
529
+ if is_index_by_table
530
+ Hash[*@cursor[":#{argument}"].fetch_hash_all.map { |row| [row.delete(:i__), row] }.flatten]
531
+ else
532
+ @cursor[":#{argument}"].fetch_hash_all
533
+ end
534
+ else
535
+ if is_index_by_table
536
+ Hash[*@cursor[":#{argument}"].fetch_all.map { |row| [row[1], row[0]] }.flatten]
537
+ else
538
+ @cursor[":#{argument}"].fetch_all.map { |row| row[0] }
539
+ end
540
+ end
541
+ else
542
+ @cursor[":#{argument}"]
543
+ end
544
+ end
545
+ end
546
+
547
+ def overload_argument_list
548
+ @overload_argument_list ||=
549
+ @skip_self ? @procedure.argument_list_without_self : @procedure.argument_list
550
+ end
551
+
552
+ def overload_arguments
553
+ @overload_arguments ||=
554
+ @skip_self ? @procedure.arguments_without_self : @procedure.arguments
555
+ end
556
+
557
+ def argument_list
558
+ @argument_list ||= overload_argument_list[@overload]
559
+ end
560
+
561
+ def arguments
562
+ @arguments ||= overload_arguments[@overload]
563
+ end
564
+
565
+ def return_metadata
566
+ @return_metadata ||= @procedure.return[@overload]
567
+ end
568
+
569
+ def out_list
570
+ @out_list ||=
571
+ @skip_self ? @procedure.out_list_without_self[@overload] : @procedure.out_list[@overload]
572
+ end
573
+
574
+ def schema_name
575
+ @schema_name ||= @procedure.schema_name
576
+ end
577
+
578
+ def package_name
579
+ @package_name ||= @procedure.package
580
+ end
581
+
582
+ def procedure_name
583
+ @procedure_name ||= @procedure.procedure
584
+ end
585
+
586
+ def dbms_output_enable_sql
587
+ @dbms_output_stream ? "DBMS_OUTPUT.ENABLE(#{@schema.dbms_output_buffer_size});\n" : ""
588
+ end
589
+
590
+ def dbms_output_lines
591
+ lines = []
592
+ if @dbms_output_stream
593
+ if (@schema.connection.database_version <=> [10, 2, 0, 0]) >= 0
594
+ cursor = @schema.connection.parse("BEGIN DBMS_OUTPUT.GET_LINES(:dbms_output_lines, :dbms_output_numlines); END;\n")
595
+ cursor.bind_param(":dbms_output_lines", nil,
596
+ data_type: "TABLE",
597
+ data_length: nil,
598
+ sql_type_name: "SYS.DBMSOUTPUT_LINESARRAY",
599
+ in_out: "OUT")
600
+ cursor.bind_param(":dbms_output_numlines", Schema::DBMS_OUTPUT_MAX_LINES, data_type: "NUMBER", in_out: "IN/OUT")
601
+ cursor.exec
602
+ lines = cursor[":dbms_output_lines"]
603
+ cursor.close
604
+ else
605
+ cursor = @schema.connection.parse("BEGIN sys.dbms_output.get_line(:line, :status); END;")
606
+ while true do
607
+ cursor.bind_param(":line", nil, data_type: "VARCHAR2", in_out: "OUT")
608
+ cursor.bind_param(":status", nil, data_type: "NUMBER", in_out: "OUT")
609
+ cursor.exec
610
+ break unless cursor[":status"] == 0
611
+ lines << cursor[":line"]
612
+ end
613
+ cursor.close
614
+ end
615
+ end
616
+ lines
617
+ end
618
+
619
+ def dbms_output_log
620
+ dbms_output_lines.each do |line|
621
+ @dbms_output_stream.puts "DBMS_OUTPUT: #{line}" if line
622
+ end
623
+ @dbms_output_stream.flush if @dbms_output_stream
624
+ end
625
+ end
626
+ end