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.
- checksums.yaml +7 -0
- data/History.txt +282 -0
- data/License.txt +20 -0
- data/README.md +263 -0
- data/VERSION +1 -0
- data/lib/plsql/connection.rb +230 -0
- data/lib/plsql/helpers.rb +7 -0
- data/lib/plsql/jdbc_connection.rb +588 -0
- data/lib/plsql/oci_connection.rb +324 -0
- data/lib/plsql/package.rb +87 -0
- data/lib/plsql/procedure.rb +584 -0
- data/lib/plsql/procedure_call.rb +626 -0
- data/lib/plsql/schema.rb +296 -0
- data/lib/plsql/sequence.rb +46 -0
- data/lib/plsql/sql_statements.rb +85 -0
- data/lib/plsql/table.rb +345 -0
- data/lib/plsql/type.rb +270 -0
- data/lib/plsql/variable.rb +143 -0
- data/lib/plsql/version.rb +3 -0
- data/lib/plsql/view.rb +38 -0
- data/lib/ruby-plsql.rb +1 -0
- data/lib/ruby_plsql.rb +13 -0
- metadata +120 -0
|
@@ -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
|