flash-gordons-ruby-plsql 0.5.0

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,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