flash-gordons-ruby-plsql 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ # apply TIMESTAMP fractional seconds patch to ruby-oci8 2.0.3
2
+ # see http://rubyforge.org/forum/forum.php?thread_id=46576&forum_id=1078
3
+ if OCI8::VERSION == "2.0.3" &&
4
+ !OCI8::BindType::Util.method_defined?(:datetime_to_array_without_timestamp_patch)
5
+
6
+ OCI8::BindType::Util.module_eval do
7
+ alias :datetime_to_array_without_timestamp_patch :datetime_to_array
8
+ def datetime_to_array(val, full)
9
+ result = datetime_to_array_without_timestamp_patch(val, full)
10
+ if result && result[6] == 0
11
+ if val.respond_to? :nsec
12
+ fsec = val.nsec
13
+ elsif val.respond_to? :usec
14
+ fsec = val.usec * 1000
15
+ else
16
+ fsec = 0
17
+ end
18
+ result[6] = fsec
19
+ end
20
+ result
21
+ end
22
+ private :datetime_to_array_without_timestamp_patch, :datetime_to_array
23
+ end
24
+
25
+ end
@@ -0,0 +1,339 @@
1
+ begin
2
+ require "oci8"
3
+ rescue LoadError
4
+ # OCI8 driver is unavailable.
5
+ msg = $!.to_s
6
+ if /-- oci8$/ =~ msg
7
+ # ruby-oci8 is not installed.
8
+ # MRI <= 1.9.2, Rubinius, JRuby:
9
+ # no such file to load -- oci8
10
+ # MRI >= 1.9.3:
11
+ # cannot load such file -- oci8
12
+ msg = "Please install ruby-oci8 gem."
13
+ end
14
+ raise LoadError, "ERROR: ruby-plsql could not load ruby-oci8 library. #{msg}"
15
+ end
16
+
17
+ require "plsql/oci8_patches"
18
+
19
+ # check ruby-oci8 version
20
+ required_oci8_version = [2, 0, 3]
21
+ oci8_version_ints = OCI8::VERSION.scan(/\d+/).map{|s| s.to_i}
22
+ if (oci8_version_ints <=> required_oci8_version) < 0
23
+ raise LoadError, "ERROR: ruby-oci8 version #{OCI8::VERSION} is too old. Please install ruby-oci8 version #{required_oci8_version.join('.')} or later."
24
+ end
25
+
26
+ module PLSQL
27
+ class OCIConnection < Connection #:nodoc:
28
+
29
+ def self.create_raw(params)
30
+ connection_string = if params[:host]
31
+ "//#{params[:host]}:#{params[:port]||1521}/#{params[:database]}"
32
+ else
33
+ params[:database]
34
+ end
35
+ new(OCI8.new(params[:username], params[:password], connection_string))
36
+ end
37
+
38
+ def logoff
39
+ super
40
+ raw_connection.logoff
41
+ end
42
+
43
+ def commit
44
+ raw_connection.commit
45
+ end
46
+
47
+ def rollback
48
+ raw_connection.rollback
49
+ end
50
+
51
+ def autocommit?
52
+ raw_connection.autocommit?
53
+ end
54
+
55
+ def autocommit=(value)
56
+ raw_connection.autocommit = value
57
+ end
58
+
59
+ def prefetch_rows=(value)
60
+ raw_connection.prefetch_rows = value
61
+ end
62
+
63
+ def exec(sql, *bindvars)
64
+ raw_connection.exec(sql, *bindvars)
65
+ true
66
+ end
67
+
68
+ class Cursor #:nodoc:
69
+ include Connection::CursorCommon
70
+
71
+ # stack of open cursors
72
+ @@open_cursors = []
73
+ attr_reader :raw_cursor
74
+
75
+ def initialize(conn, raw_cursor)
76
+ @connection = conn
77
+ @raw_cursor = raw_cursor
78
+ @@open_cursors.push self
79
+ end
80
+
81
+ def self.new_from_parse(conn, sql)
82
+ raw_cursor = conn.raw_connection.parse(sql)
83
+ self.new(conn, raw_cursor)
84
+ end
85
+
86
+ def self.new_from_query(conn, sql, bindvars=[], options={})
87
+ cursor = new_from_parse(conn, sql)
88
+ if prefetch_rows = options[:prefetch_rows]
89
+ cursor.prefetch_rows = prefetch_rows
90
+ end
91
+ cursor.exec(*bindvars)
92
+ cursor
93
+ end
94
+
95
+ def prefetch_rows=(value)
96
+ @raw_cursor.prefetch_rows = value
97
+ end
98
+
99
+ def bind_param(arg, value, metadata)
100
+ type, length = @connection.plsql_to_ruby_data_type(metadata)
101
+ ora_value = @connection.ruby_value_to_ora_value(value, type)
102
+ @raw_cursor.bind_param(arg, ora_value, type, length)
103
+ end
104
+
105
+ def exec(*bindvars)
106
+ @raw_cursor.exec(*bindvars)
107
+ end
108
+
109
+ def [](key)
110
+ @connection.ora_value_to_ruby_value(@raw_cursor[key])
111
+ end
112
+
113
+ def fetch
114
+ row = @raw_cursor.fetch
115
+ row && row.map{|v| @connection.ora_value_to_ruby_value(v)}
116
+ end
117
+
118
+ def fields
119
+ @fields ||= @raw_cursor.get_col_names.map{|c| c.downcase.to_sym}
120
+ end
121
+
122
+ def close_raw_cursor
123
+ @raw_cursor.close
124
+ end
125
+
126
+ def close
127
+ # close all cursors that were created after this one
128
+ while (open_cursor = @@open_cursors.pop) && !open_cursor.equal?(self)
129
+ open_cursor.close_raw_cursor
130
+ end
131
+ close_raw_cursor
132
+ end
133
+
134
+ end
135
+
136
+ def parse(sql)
137
+ Cursor.new_from_parse(self, sql)
138
+ end
139
+
140
+ def cursor_from_query(sql, bindvars=[], options={})
141
+ Cursor.new_from_query(self, sql, bindvars, options)
142
+ end
143
+
144
+ def plsql_to_ruby_data_type(metadata)
145
+ data_type, data_length = metadata[:data_type], metadata[:data_length]
146
+ case data_type
147
+ when "VARCHAR2", "CHAR", "NVARCHAR2", "NCHAR"
148
+ [String, data_length || 32767]
149
+ when "CLOB", "NCLOB"
150
+ [OCI8::CLOB, nil]
151
+ when "BLOB"
152
+ [OCI8::BLOB, nil]
153
+ when "NUMBER", "PLS_INTEGER", "BINARY_INTEGER"
154
+ [OraNumber, nil]
155
+ when "DATE"
156
+ [DateTime, nil]
157
+ when "TIMESTAMP", "TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH LOCAL TIME ZONE"
158
+ [Time, nil]
159
+ when "TABLE", "VARRAY", "OBJECT"
160
+ # create Ruby class for collection
161
+ klass = OCI8::Object::Base.get_class_by_typename(metadata[:sql_type_name])
162
+ unless klass
163
+ klass = Class.new(OCI8::Object::Base)
164
+ klass.set_typename metadata[:sql_type_name]
165
+ end
166
+ [klass, nil]
167
+ when "REF CURSOR"
168
+ [OCI8::Cursor]
169
+ else
170
+ [String, 32767]
171
+ end
172
+ end
173
+
174
+ def ruby_value_to_ora_value(value, type=nil)
175
+ type ||= value.class
176
+ case type.to_s.to_sym
177
+ when :Fixnum, :BigDecimal, :String
178
+ value
179
+ when :OraNumber
180
+ # pass parameters as OraNumber to avoid rounding errors
181
+ case value
182
+ when Bignum
183
+ OraNumber.new(value.to_s)
184
+ when BigDecimal
185
+ OraNumber.new(value.to_s('F'))
186
+ when TrueClass
187
+ OraNumber.new(1)
188
+ when FalseClass
189
+ OraNumber.new(0)
190
+ else
191
+ value
192
+ end
193
+ when :DateTime
194
+ case value
195
+ when Time
196
+ ::DateTime.civil(value.year, value.month, value.day, value.hour, value.min, value.sec, Rational(value.utc_offset, 86400))
197
+ when DateTime
198
+ value
199
+ when Date
200
+ ::DateTime.civil(value.year, value.month, value.day, 0, 0, 0, 0)
201
+ else
202
+ value
203
+ end
204
+ when :"OCI8::CLOB", :"OCI8::BLOB"
205
+ # ruby-oci8 cannot create CLOB/BLOB from ''
206
+ value.to_s.length > 0 ? type.new(raw_oci_connection, value) : nil
207
+ when :"OCI8::Cursor"
208
+ value && value.raw_cursor
209
+ else
210
+ # collections and object types
211
+ if type.superclass == OCI8::Object::Base
212
+ return nil if value.nil?
213
+ tdo = raw_oci_connection.get_tdo_by_class(type)
214
+ if tdo.is_collection?
215
+ raise ArgumentError, "You should pass Array value for collection type parameter" unless value.is_a?(Array)
216
+ elem_list = value.map do |elem|
217
+ if (attr_tdo = tdo.coll_attr.typeinfo)
218
+ attr_type, attr_length = plsql_to_ruby_data_type(:data_type => 'OBJECT', :sql_type_name => attr_tdo.typename)
219
+ else
220
+ attr_type = elem.class
221
+ end
222
+ ruby_value_to_ora_value(elem, attr_type)
223
+ end
224
+ # construct collection value
225
+ # TODO: change setting instance variable to appropriate ruby-oci8 method call when available
226
+ collection = type.new(raw_oci_connection)
227
+ collection.instance_variable_set('@attributes', elem_list)
228
+ collection
229
+ else # object type
230
+ raise ArgumentError, "You should pass Hash value for object type parameter" unless value.is_a?(Hash)
231
+ object_attrs = value.dup
232
+ object_attrs.keys.each do |key|
233
+ raise ArgumentError, "Wrong object type field passed to PL/SQL procedure" unless (attr = tdo.attr_getters[key])
234
+ case attr.datatype
235
+ when OCI8::TDO::ATTR_NAMED_TYPE, OCI8::TDO::ATTR_NAMED_COLLECTION
236
+ # nested object type or collection
237
+ attr_type, attr_length = plsql_to_ruby_data_type(:data_type => 'OBJECT', :sql_type_name => attr.typeinfo.typename)
238
+ object_attrs[key] = ruby_value_to_ora_value(object_attrs[key], attr_type)
239
+ end
240
+ end
241
+ type.new(raw_oci_connection, object_attrs)
242
+ end
243
+ # all other cases
244
+ else
245
+ value
246
+ end
247
+ end
248
+ end
249
+
250
+ def ora_value_to_ruby_value(value)
251
+ case value
252
+ when Float, OraNumber, BigDecimal
253
+ ora_number_to_ruby_number(value)
254
+ when DateTime, OraDate
255
+ ora_date_to_ruby_date(value)
256
+ when OCI8::LOB
257
+ if value.available?
258
+ value.rewind
259
+ value.read
260
+ else
261
+ nil
262
+ end
263
+ when OCI8::Object::Base
264
+ tdo = raw_oci_connection.get_tdo_by_class(value.class)
265
+ if tdo.is_collection?
266
+ value.to_ary.map{|e| ora_value_to_ruby_value(e)}
267
+ else # object type
268
+ tdo.attributes.inject({}) do |hash, attr|
269
+ hash[attr.name] = ora_value_to_ruby_value(value.instance_variable_get(:@attributes)[attr.name])
270
+ hash
271
+ end
272
+ end
273
+ when OCI8::Cursor
274
+ Cursor.new(self, value)
275
+ else
276
+ value
277
+ end
278
+ end
279
+
280
+ def describe_synonym(schema_name, synonym_name)
281
+ if schema_name == 'PUBLIC'
282
+ full_name = synonym_name.to_s
283
+ else
284
+ full_name = "#{schema_name}.#{synonym_name}"
285
+ end
286
+ metadata = raw_connection.describe_synonym(full_name)
287
+ [metadata.schema_name, metadata.name]
288
+ rescue OCIError
289
+ nil
290
+ end
291
+
292
+ def database_version
293
+ @database_version ||= (version = raw_connection.oracle_server_version) &&
294
+ [version.major, version.minor, version.update, version.patch]
295
+ end
296
+
297
+ private
298
+
299
+ def raw_oci_connection
300
+ if raw_connection.is_a? OCI8
301
+ raw_connection
302
+ # ActiveRecord Oracle enhanced adapter puts OCI8EnhancedAutoRecover wrapper around OCI8
303
+ # in this case we need to pass original OCI8 connection
304
+ else
305
+ raw_connection.instance_variable_get(:@connection)
306
+ end
307
+ end
308
+
309
+ def ora_number_to_ruby_number(num)
310
+ # return BigDecimal instead of Float to avoid rounding errors
311
+ num == (num_to_i = num.to_i) ? num_to_i : (num.is_a?(BigDecimal) ? num : BigDecimal.new(num.to_s))
312
+ end
313
+
314
+ def ora_date_to_ruby_date(val)
315
+ case val
316
+ when DateTime
317
+ # similar implementation as in oracle_enhanced adapter
318
+ begin
319
+ Time.send(plsql.default_timezone, val.year, val.month, val.day, val.hour, val.min, val.sec)
320
+ rescue
321
+ offset = plsql.default_timezone.to_sym == :local ? plsql.local_timezone_offset : 0
322
+ DateTime.civil(val.year, val.month, val.day, val.hour, val.min, val.sec, offset)
323
+ end
324
+ when OraDate
325
+ # similar implementation as in oracle_enhanced adapter
326
+ begin
327
+ Time.send(plsql.default_timezone, val.year, val.month, val.day, val.hour, val.minute, val.second)
328
+ rescue
329
+ offset = plsql.default_timezone.to_sym == :local ? plsql.local_timezone_offset : 0
330
+ DateTime.civil(val.year, val.month, val.day, val.hour, val.minute, val.second, offset)
331
+ end
332
+ else
333
+ val
334
+ end
335
+ end
336
+
337
+ end
338
+
339
+ end
@@ -0,0 +1,80 @@
1
+ module PLSQL
2
+ module PackageClassMethods #:nodoc:
3
+ def find(schema, package_name)
4
+ find_package_in_schema(schema, package_name) || find_package_by_synonym(schema, package_name)
5
+ end
6
+
7
+ def find_by_db_object(db_object)
8
+ find(db_object.schema, db_object.name)
9
+ end
10
+
11
+ def find_package_in_schema(schema, package_name)
12
+ row = schema.select_first(<<-SQL, schema.schema_name, package_name.to_s.upcase)
13
+ SELECT object_name
14
+ FROM all_objects
15
+ WHERE owner = :owner
16
+ AND object_name = :package
17
+ AND object_type = 'PACKAGE'
18
+ SQL
19
+ new(schema, package_name) if row
20
+ end
21
+
22
+ def find_package_by_synonym(schema, package_name)
23
+ row = schema.select_first(<<-SQL, schema.schema_name, package_name.to_s.upcase)
24
+ SELECT o.owner, o.object_name
25
+ FROM all_synonyms s,
26
+ all_objects o
27
+ WHERE s.owner IN (:owner, 'PUBLIC')
28
+ AND s.synonym_name = :synonym_name
29
+ AND o.owner = s.table_owner
30
+ AND o.object_name = s.table_name
31
+ AND o.object_type = 'PACKAGE'
32
+ ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)
33
+ SQL
34
+ new(schema, row[1], row[0]) if row
35
+ end
36
+ end
37
+
38
+ class Package #:nodoc:
39
+ extend PackageClassMethods
40
+
41
+ def initialize(schema, package, override_schema_name = nil)
42
+ @schema = schema
43
+ @override_schema_name = override_schema_name
44
+ @package = package.to_s.upcase
45
+ @package_objects = {}
46
+ end
47
+
48
+ def [](object_name)
49
+ object_name = object_name.to_s
50
+ @package_objects[object_name] ||= [Procedure, Variable, PipelinedFunction].inject(nil) do |res, object_type|
51
+ res || object_type.find(@schema, object_name, @package, @override_schema_name)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def method_missing(method, *args, &block)
58
+ method = method.to_s
59
+ method.chop! if (assignment = method[/=$/])
60
+
61
+ case (object = self[method])
62
+ when Procedure, PipelinedFunction
63
+ raise ArgumentError, "Cannot assign value to package procedure '#{method.upcase}'" if assignment
64
+ object.exec(*args, &block)
65
+ when Variable
66
+ if assignment
67
+ raise ArgumentError, "Just one value can be assigned to package variable '#{method.upcase}'" unless args.size == 1 && block.nil?
68
+ object.value = args[0]
69
+ else
70
+ raise ArgumentError, "Cannot pass arguments when getting package variable '#{method.upcase}' value" unless args.size == 0 && block.nil?
71
+ object.value
72
+ end
73
+ else
74
+ raise ArgumentError, "No PL/SQL procedure or variable '#{method.upcase}' found"
75
+ end
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,269 @@
1
+ module PLSQL
2
+
3
+ module ProcedureClassMethods #:nodoc:
4
+ def find(schema, procedure_name, package_name = nil, override_schema_name = nil)
5
+ if package_name
6
+ find_procedure_in_package(schema, package_name, procedure_name, override_schema_name)
7
+ else
8
+ find_procedure_in_schema(schema, procedure_name) || find_procedure_by_synonym(schema, procedure_name)
9
+ end
10
+ end
11
+
12
+ def find_procedure_in_schema(schema, procedure_name)
13
+ row = schema.select_first(<<-SQL, schema.schema_name, procedure_name.to_s.upcase)
14
+ SELECT object_id
15
+ FROM all_procedures
16
+ WHERE owner = :owner
17
+ AND object_name = :object_name
18
+ AND object_type IN ('PROCEDURE', 'FUNCTION')
19
+ AND pipelined = 'NO'
20
+ SQL
21
+ new(schema, procedure_name, nil, nil, row[0]) if row
22
+ end
23
+
24
+ def find_procedure_by_synonym(schema, procedure_name)
25
+ row = schema.select_first(<<-SQL, schema.schema_name, procedure_name.to_s.upcase)
26
+ SELECT p.owner, p.object_name, p.object_id
27
+ FROM all_synonyms s,
28
+ all_procedures p
29
+ WHERE s.owner IN (:owner, 'PUBLIC')
30
+ AND s.synonym_name = :synonym_name
31
+ AND p.owner = s.table_owner
32
+ AND p.object_name = s.table_name
33
+ AND p.object_type IN ('PROCEDURE','FUNCTION')
34
+ AND p.pipelined = 'NO'
35
+ ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)
36
+ SQL
37
+ new(schema, row[1], nil, row[0], row[2]) if row
38
+ end
39
+
40
+ def find_procedure_in_package(schema, package_name, procedure_name, override_schema_name = nil)
41
+ schema_name = override_schema_name || schema.schema_name
42
+ row = schema.select_first(<<-SQL, schema_name, package_name, procedure_name.to_s.upcase)
43
+ SELECT o.object_id
44
+ FROM all_procedures p,
45
+ all_objects o
46
+ WHERE p.owner = :owner
47
+ AND p.object_name = :object_name
48
+ AND p.procedure_name = :procedure_name
49
+ AND p.pipelined = 'NO'
50
+ AND o.owner = p.owner
51
+ AND o.object_name = p.object_name
52
+ AND o.object_type = 'PACKAGE'
53
+ SQL
54
+ new(schema, procedure_name, package_name, override_schema_name, row[0]) if row
55
+ end
56
+ end
57
+
58
+ module ProcedureCommon #:nodoc:
59
+ attr_reader :arguments, :argument_list, :out_list, :return
60
+ attr_reader :schema, :schema_name, :package, :procedure
61
+
62
+ # return type string from metadata that can be used in DECLARE block or table definition
63
+ def self.type_to_sql(metadata) #:nodoc:
64
+ case metadata[:data_type]
65
+ when 'NUMBER'
66
+ precision, scale = metadata[:data_precision], metadata[:data_scale]
67
+ "NUMBER#{precision ? "(#{precision}#{scale ? ",#{scale}": ""})" : ""}"
68
+ when 'VARCHAR2', 'CHAR'
69
+ length = case metadata[:char_used]
70
+ when 'C' then "#{metadata[:char_length]} CHAR"
71
+ when 'B' then "#{metadata[:data_length]} BYTE"
72
+ else
73
+ metadata[:data_length]
74
+ end
75
+ "#{metadata[:data_type]}#{length && "(#{length})"}"
76
+ when 'NVARCHAR2', 'NCHAR'
77
+ length = metadata[:char_length]
78
+ "#{metadata[:data_type]}#{length && "(#{length})"}"
79
+ when 'PL/SQL TABLE', 'TABLE', 'VARRAY', 'OBJECT'
80
+ metadata[:sql_type_name]
81
+ else
82
+ metadata[:data_type]
83
+ end
84
+ end
85
+
86
+ # get procedure argument metadata from data dictionary
87
+ def get_argument_metadata #:nodoc:
88
+ @arguments = {}
89
+ @argument_list = {}
90
+ @out_list = {}
91
+ @return = {}
92
+ @overloaded = false
93
+
94
+ # store reference to previous level record or collection metadata
95
+ previous_level_argument_metadata = {}
96
+
97
+ # store tmp tables for each overload for table parameters with types defined inside packages
98
+ @tmp_table_names = {}
99
+ # store if tmp tables are created for specific overload
100
+ @tmp_tables_created = {}
101
+
102
+ # subprogram_id column is available just from version 10g
103
+ subprogram_id_column = (@schema.connection.database_version <=> [10, 2, 0, 2]) >= 0 ? 'subprogram_id' : 'NULL'
104
+
105
+ @schema.select_all(
106
+ "SELECT #{subprogram_id_column}, object_name, TO_NUMBER(overload), argument_name, position, data_level,
107
+ data_type, in_out, data_length, data_precision, data_scale, char_used,
108
+ char_length, type_owner, type_name, type_subname
109
+ FROM all_arguments
110
+ WHERE object_id = :object_id
111
+ AND owner = :owner
112
+ AND object_name = :procedure_name
113
+ ORDER BY overload, sequence",
114
+ @object_id, @schema_name, @procedure
115
+ ) do |r|
116
+
117
+ subprogram_id, object_name, overload, argument_name, position, data_level,
118
+ data_type, in_out, data_length, data_precision, data_scale, char_used,
119
+ char_length, type_owner, type_name, type_subname = r
120
+
121
+ @overloaded ||= !overload.nil?
122
+ # if not overloaded then store arguments at key 0
123
+ overload ||= 0
124
+ @arguments[overload] ||= {}
125
+ @return[overload] ||= nil
126
+ @tmp_table_names[overload] ||= []
127
+
128
+ sql_type_name = type_owner && "#{type_owner == 'PUBLIC' ? nil : "#{type_owner}."}#{type_name}#{type_subname ? ".#{type_subname}" : nil}"
129
+
130
+ tmp_table_name = nil
131
+ # type defined inside package
132
+ if type_subname
133
+ if collection_type?(data_type)
134
+ raise ArgumentError, "#{data_type} type #{sql_type_name} definition inside package is not supported as part of other type definition," <<
135
+ " use CREATE TYPE outside package" if data_level > 0
136
+ # if subprogram_id was not supported by all_arguments view
137
+ # then generate unique ID from object_name and overload
138
+ subprogram_id ||= "#{object_name.hash % 10000}#{overload}"
139
+ tmp_table_name = "#{Connection::RUBY_TEMP_TABLE_PREFIX}#{@schema.connection.session_id}_#{@object_id}_#{subprogram_id}_#{position}"
140
+ elsif data_type != 'PL/SQL RECORD'
141
+ # raise exception only when there are no overloaded procedure definitions
142
+ # (as probably this overload will not be used at all)
143
+ raise ArgumentError, "Parameter type #{sql_type_name} definition inside package is not supported, use CREATE TYPE outside package" if overload == 0
144
+ end
145
+ end
146
+
147
+ argument_metadata = {
148
+ :position => position && position.to_i,
149
+ :data_type => data_type,
150
+ :in_out => in_out,
151
+ :data_length => data_length && data_length.to_i,
152
+ :data_precision => data_precision && data_precision.to_i,
153
+ :data_scale => data_scale && data_scale.to_i,
154
+ :char_used => char_used,
155
+ :char_length => char_length && char_length.to_i,
156
+ :type_owner => type_owner,
157
+ :type_name => type_name,
158
+ :type_subname => type_subname,
159
+ :sql_type_name => sql_type_name
160
+ }
161
+ if tmp_table_name
162
+ @tmp_table_names[overload] << [(argument_metadata[:tmp_table_name] = tmp_table_name), argument_metadata]
163
+ end
164
+
165
+ if composite_type?(data_type)
166
+ case data_type
167
+ when 'PL/SQL RECORD'
168
+ argument_metadata[:fields] = {}
169
+ end
170
+ previous_level_argument_metadata[data_level] = argument_metadata
171
+ end
172
+
173
+ # if function has return value
174
+ if argument_name.nil? && data_level == 0 && in_out == 'OUT'
175
+ @return[overload] = argument_metadata
176
+ # if parameter
177
+ else
178
+ # top level parameter
179
+ if data_level == 0
180
+ # sometime there are empty IN arguments in all_arguments view for procedures without arguments (e.g. for DBMS_OUTPUT.DISABLE)
181
+ @arguments[overload][argument_name.downcase.to_sym] = argument_metadata if argument_name
182
+ # or lower level part of composite type
183
+ else
184
+ case previous_level_argument_metadata[data_level - 1][:data_type]
185
+ when 'PL/SQL RECORD'
186
+ previous_level_argument_metadata[data_level - 1][:fields][argument_name.downcase.to_sym] = argument_metadata
187
+ when 'PL/SQL TABLE', 'TABLE', 'VARRAY', 'REF CURSOR'
188
+ previous_level_argument_metadata[data_level - 1][:element] = argument_metadata
189
+ end
190
+ end
191
+ end
192
+ end
193
+ # if procedure is without arguments then create default empty argument list for default overload
194
+ @arguments[0] = {} if @arguments.keys.empty?
195
+
196
+ construct_argument_list_for_overloads
197
+ end
198
+
199
+ def construct_argument_list_for_overloads #:nodoc:
200
+ @overloads = @arguments.keys.sort
201
+ @overloads.each do |overload|
202
+ @argument_list[overload] = @arguments[overload].keys.sort {|k1, k2| @arguments[overload][k1][:position] <=> @arguments[overload][k2][:position]}
203
+ @out_list[overload] = @argument_list[overload].select {|k| @arguments[overload][k][:in_out] =~ /OUT/}
204
+ end
205
+ end
206
+
207
+ def ensure_tmp_tables_created(overload) #:nodoc:
208
+ return if @tmp_tables_created.nil? || @tmp_tables_created[overload]
209
+ @tmp_table_names[overload] && @tmp_table_names[overload].each do |table_name, argument_metadata|
210
+ sql = "CREATE GLOBAL TEMPORARY TABLE #{table_name} (\n"
211
+ element_metadata = argument_metadata[:element]
212
+ case element_metadata[:data_type]
213
+ when 'PL/SQL RECORD'
214
+ fields_metadata = element_metadata[:fields]
215
+ fields_sorted_by_position = fields_metadata.keys.sort_by{|k| fields_metadata[k][:position]}
216
+ sql << fields_sorted_by_position.map do |field|
217
+ metadata = fields_metadata[field]
218
+ "#{field} #{ProcedureCommon.type_to_sql(metadata)}"
219
+ end.join(",\n")
220
+ else
221
+ sql << "element #{ProcedureCommon.type_to_sql(element_metadata)}"
222
+ end
223
+ sql << ",\ni__ NUMBER(38)\n"
224
+ sql << ") ON COMMIT PRESERVE ROWS\n"
225
+ sql_block = "DECLARE\nPRAGMA AUTONOMOUS_TRANSACTION;\nBEGIN\nEXECUTE IMMEDIATE :sql;\nEND;\n"
226
+ @schema.execute sql_block, sql
227
+ end
228
+ @tmp_tables_created[overload] = true
229
+ end
230
+
231
+ PLSQL_COMPOSITE_TYPES = ['PL/SQL RECORD', 'PL/SQL TABLE', 'TABLE', 'VARRAY', 'REF CURSOR'].freeze
232
+ def composite_type?(data_type) #:nodoc:
233
+ PLSQL_COMPOSITE_TYPES.include? data_type
234
+ end
235
+
236
+ PLSQL_COLLECTION_TYPES = ['PL/SQL TABLE', 'TABLE', 'VARRAY'].freeze
237
+ def collection_type?(data_type) #:nodoc:
238
+ PLSQL_COLLECTION_TYPES.include? data_type
239
+ end
240
+
241
+ def overloaded? #:nodoc:
242
+ @overloaded
243
+ end
244
+ end
245
+
246
+ class Procedure #:nodoc:
247
+ extend ProcedureClassMethods
248
+ include ProcedureCommon
249
+
250
+ attr_reader :arguments, :argument_list, :out_list, :return
251
+ attr_reader :schema, :schema_name, :package, :procedure
252
+
253
+ def initialize(schema, procedure, package, override_schema_name, object_id)
254
+ @schema = schema
255
+ @schema_name = override_schema_name || schema.schema_name
256
+ @procedure = procedure.to_s.upcase
257
+ @package = package
258
+ @object_id = object_id
259
+
260
+ get_argument_metadata
261
+ end
262
+
263
+ def exec(*args, &block)
264
+ call = ProcedureCall.new(self, args)
265
+ call.exec(&block)
266
+ end
267
+
268
+ end
269
+ end