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,87 @@
1
+ module PLSQL
2
+ module SQLStatements
3
+ # Select first row as array or values (without column names)
4
+ def select_first(sql, *bindvars)
5
+ @connection.select_first(sql, *bindvars)
6
+ end
7
+
8
+ # Select all rows as array or values (without column names)
9
+ def select_all(sql, *bindvars, &block)
10
+ @connection.select_all(sql, *bindvars, &block)
11
+ end
12
+
13
+ # Select one value (use if only one row with one value is selected)
14
+ def select_one(sql, *bindvars)
15
+ (row = @connection.select_first(sql, *bindvars)) && row[0]
16
+ end
17
+
18
+ # Select :first or :all values. Examples:
19
+ #
20
+ # plsql.select :first, "SELECT * FROM employees WHERE employee_id = :1", 1
21
+ # plsql.select :all, "SELECT * FROM employees ORDER BY employee_id"
22
+ def select(*args)
23
+ case args[0]
24
+ when nil
25
+ raise ArgumentError, "Not enough arguments"
26
+ when :first
27
+ args.shift
28
+ @connection.select_hash_first(*args)
29
+ when :all
30
+ args.shift
31
+ @connection.select_hash_all(*args)
32
+ else
33
+ @connection.select_hash_all(*args)
34
+ end
35
+ end
36
+
37
+ # Execute SQL statement. Example:
38
+ #
39
+ # plsql.execute "DROP TABLE employees"
40
+ def execute(*args)
41
+ @connection.exec(*args)
42
+ end
43
+
44
+ # Execute COMMIT in current database session.
45
+ # Use beforehand
46
+ #
47
+ # plsql.connection.autocommit = false
48
+ #
49
+ # to turn off automatic commits after each statement.
50
+ def commit
51
+ @connection.commit
52
+ end
53
+
54
+ # Execute ROLLBACK in current database session.
55
+ # Use beforehand
56
+ #
57
+ # plsql.connection.autocommit = false
58
+ #
59
+ # to turn off automatic commits after each statement.
60
+ def rollback
61
+ @connection.rollback
62
+ end
63
+
64
+ # Create SAVEPOINT with specified name. Later use +rollback_to+ method to roll changes back
65
+ # to specified savepoint.
66
+ # Use beforehand
67
+ #
68
+ # plsql.connection.autocommit = false
69
+ #
70
+ # to turn off automatic commits after each statement.
71
+ def savepoint(name)
72
+ execute "SAVEPOINT #{name}"
73
+ end
74
+
75
+ # Roll back changes to specified savepoint (that was created using +savepoint+ method)
76
+ # Use beforehand
77
+ #
78
+ # plsql.connection.autocommit = false
79
+ #
80
+ # to turn off automatic commits after each statement.
81
+ def rollback_to(name)
82
+ execute "ROLLBACK TO #{name}"
83
+ end
84
+
85
+ end
86
+ end
87
+
@@ -0,0 +1,348 @@
1
+ module PLSQL
2
+
3
+ module TableClassMethods #:nodoc:
4
+ def find(schema, table)
5
+ if schema.select_first(
6
+ "SELECT table_name FROM all_tables
7
+ WHERE owner = :owner
8
+ AND table_name = :table_name",
9
+ schema.schema_name, table.to_s.upcase)
10
+ new(schema, table)
11
+ # search for synonym
12
+ elsif (row = schema.select_first(
13
+ "SELECT t.owner, t.table_name
14
+ FROM all_synonyms s, all_tables t
15
+ WHERE s.owner = :owner
16
+ AND s.synonym_name = :synonym_name
17
+ AND t.owner = s.table_owner
18
+ AND t.table_name = s.table_name
19
+ UNION ALL
20
+ SELECT t.owner, t.table_name
21
+ FROM all_synonyms s, all_tables t
22
+ WHERE s.owner = 'PUBLIC'
23
+ AND s.synonym_name = :synonym_name
24
+ AND t.owner = s.table_owner
25
+ AND t.table_name = s.table_name",
26
+ schema.schema_name, table.to_s.upcase, table.to_s.upcase))
27
+ new(schema, row[1], row[0])
28
+ else
29
+ nil
30
+ end
31
+ end
32
+ end
33
+
34
+ class Table
35
+ extend TableClassMethods
36
+
37
+ attr_reader :columns, :schema_name, :table_name #:nodoc:
38
+
39
+ def initialize(schema, table, override_schema_name = nil) #:nodoc:
40
+ @schema = schema
41
+ @schema_name = override_schema_name || schema.schema_name
42
+ @table_name = table.to_s.upcase
43
+ @columns = {}
44
+
45
+ @schema.select_all(
46
+ "SELECT c.column_name, c.column_id position,
47
+ c.data_type, c.data_length, c.data_precision, c.data_scale, c.char_used,
48
+ c.data_type_owner, c.data_type_mod,
49
+ CASE WHEN c.data_type_owner IS NULL THEN NULL
50
+ ELSE (SELECT t.typecode FROM all_types t
51
+ WHERE t.owner = c.data_type_owner
52
+ AND t.type_name = c.data_type) END typecode,
53
+ c.nullable, c.data_default
54
+ FROM all_tab_columns c
55
+ WHERE c.owner = :owner
56
+ AND c.table_name = :table_name",
57
+ @schema_name, @table_name
58
+ ) do |r|
59
+ column_name, position,
60
+ data_type, data_length, data_precision, data_scale, char_used,
61
+ data_type_owner, data_type_mod, typecode, nullable, data_default = r
62
+ # remove scale (n) from data_type (returned for TIMESTAMPs and INTERVALs)
63
+ data_type.sub!(/\(\d+\)/,'')
64
+ # store column metadata
65
+ @columns[column_name.downcase.to_sym] = {
66
+ :position => position && position.to_i,
67
+ :data_type => data_type_owner && (typecode == 'COLLECTION' ? 'TABLE' : 'OBJECT' ) || data_type,
68
+ :data_length => data_type_owner ? nil : data_length && data_length.to_i,
69
+ :data_precision => data_precision && data_precision.to_i,
70
+ :data_scale => data_scale && data_scale.to_i,
71
+ :char_used => char_used,
72
+ :type_owner => data_type_owner,
73
+ :type_name => data_type_owner && data_type,
74
+ :sql_type_name => data_type_owner && "#{data_type_owner}.#{data_type}",
75
+ :nullable => nullable == 'Y', # store as true or false
76
+ :data_default => data_default && data_default.strip # remove leading and trailing whitespace
77
+ }
78
+ end
79
+ end
80
+
81
+ # list of table column names
82
+ def column_names
83
+ @column_names ||= @columns.keys.sort_by{|k| columns[k][:position]}
84
+ end
85
+
86
+ # General select method with :first, :all or :count as first parameter.
87
+ # It is recommended to use #first, #all or #count method instead of this one.
88
+ def select(first_or_all, sql_params='', *bindvars)
89
+ case first_or_all
90
+ when :first, :all
91
+ select_sql = "SELECT * "
92
+ when :count
93
+ select_sql = "SELECT COUNT(*) "
94
+ else
95
+ raise ArgumentError, "Only :first, :all or :count are supported"
96
+ end
97
+ select_sql << "FROM \"#{@schema_name}\".\"#{@table_name}\" "
98
+ case sql_params
99
+ when String
100
+ select_sql << sql_params
101
+ when Hash
102
+ raise ArgumentError, "Cannot specify bind variables when passing WHERE conditions as Hash" unless bindvars.empty?
103
+ where_sqls = []
104
+ order_by_sql = nil
105
+ sql_params.each do |k,v|
106
+ if k == :order_by
107
+ order_by_sql = " ORDER BY #{v} "
108
+ elsif v.nil? || v == :is_null
109
+ where_sqls << "#{k} IS NULL"
110
+ elsif v == :is_not_null
111
+ where_sqls << "#{k} IS NOT NULL"
112
+ else
113
+ where_sqls << "#{k} = :#{k}"
114
+ bindvars << v
115
+ end
116
+ end
117
+ select_sql << "WHERE " << where_sqls.join(' AND ') unless where_sqls.empty?
118
+ select_sql << order_by_sql if order_by_sql
119
+ else
120
+ raise ArgumentError, "Only String or Hash can be provided as SQL condition argument"
121
+ end
122
+ if first_or_all == :count
123
+ @schema.select_one(select_sql, *bindvars)
124
+ else
125
+ @schema.select(first_or_all, select_sql, *bindvars)
126
+ end
127
+ end
128
+
129
+ # Select all table records using optional conditions. Examples:
130
+ #
131
+ # plsql.employees.all
132
+ # plsql.employees.all(:order_by => :employee_id)
133
+ # plsql.employees.all("WHERE employee_id > :employee_id", 5)
134
+ #
135
+ def all(sql='', *bindvars)
136
+ select(:all, sql, *bindvars)
137
+ end
138
+
139
+ # Select first table record using optional conditions. Examples:
140
+ #
141
+ # plsql.employees.first
142
+ # plsql.employees.first(:employee_id => 1)
143
+ # plsql.employees.first("WHERE employee_id = 1")
144
+ # plsql.employees.first("WHERE employee_id = :employee_id", 1)
145
+ #
146
+ def first(sql='', *bindvars)
147
+ select(:first, sql, *bindvars)
148
+ end
149
+
150
+ # Count table records using optional conditions. Examples:
151
+ #
152
+ # plsql.employees.count
153
+ # plsql.employees.count("WHERE employee_id > :employee_id", 5)
154
+ #
155
+ def count(sql='', *bindvars)
156
+ select(:count, sql, *bindvars)
157
+ end
158
+
159
+ # Insert record or records in table. Examples:
160
+ #
161
+ # employee = { :employee_id => 1, :first_name => 'First', :last_name => 'Last', :hire_date => Time.local(2000,01,31) }
162
+ # plsql.employees.insert employee
163
+ # # => INSERT INTO employees VALUES (1, 'First', 'Last', ...)
164
+ #
165
+ # employees = [employee1, employee2, ... ] # array of many Hashes
166
+ # plsql.employees.insert employees
167
+ #
168
+ def insert(record)
169
+ # if Array of records is passed then insert each individually
170
+ if record.is_a?(Array)
171
+ record.each {|r| insert(r)}
172
+ return nil
173
+ end
174
+
175
+ table_proc = TableProcedure.new(@schema, self, :insert)
176
+ table_proc.add_insert_arguments(record)
177
+
178
+ call = ProcedureCall.new(table_proc, table_proc.argument_values)
179
+ call.exec
180
+ end
181
+
182
+ # Insert record or records in table using array of values. Examples:
183
+ #
184
+ # # with values for all columns
185
+ # plsql.employees.insert_values [1, 'First', 'Last', Time.local(2000,01,31)]
186
+ # # => INSERT INTO employees VALUES (1, 'First', 'Last', ...)
187
+ #
188
+ # # with values for specified columns
189
+ # plsql.employees.insert_values [:employee_id, :first_name, :last_name], [1, 'First', 'Last']
190
+ # # => INSERT INTO employees (employee_id, first_name, last_name) VALUES (1, 'First', 'Last')
191
+ #
192
+ # # with values for many records
193
+ # plsql.employees.insert_values [:employee_id, :first_name, :last_name], [1, 'First', 'Last'], [2, 'Second', 'Last']
194
+ # # => INSERT INTO employees (employee_id, first_name, last_name) VALUES (1, 'First', 'Last')
195
+ # # => INSERT INTO employees (employee_id, first_name, last_name) VALUES (2, 'Second', 'Last')
196
+ #
197
+ def insert_values(*args)
198
+ raise ArgumentError, "no arguments given" unless args.first
199
+ # if first argument is array of symbols then use it as list of fields
200
+ if args.first.all?{|a| a.instance_of?(Symbol)}
201
+ fields = args.shift
202
+ # otherwise use all columns as list of fields
203
+ else
204
+ fields = column_names
205
+ end
206
+ args.each do |record|
207
+ raise ArgumentError, "record should be Array of values" unless record.is_a?(Array)
208
+ raise ArgumentError, "wrong number of column values" unless record.size == fields.size
209
+ insert(ArrayHelpers::to_hash(fields, record))
210
+ end
211
+ end
212
+
213
+ # Update table records using optional conditions. Example:
214
+ #
215
+ # plsql.employees.update(:first_name => 'Second', :where => {:employee_id => 1})
216
+ # # => UPDATE employees SET first_name = 'Second' WHERE employee_id = 1
217
+ #
218
+ def update(params)
219
+ raise ArgumentError, "Only Hash parameter can be passed to table update method" unless params.is_a?(Hash)
220
+ where = params.delete(:where)
221
+
222
+ table_proc = TableProcedure.new(@schema, self, :update)
223
+ table_proc.add_set_arguments(params)
224
+ table_proc.add_where_arguments(where) if where
225
+ call = ProcedureCall.new(table_proc, table_proc.argument_values)
226
+ call.exec
227
+ end
228
+
229
+ # Delete table records using optional conditions. Example:
230
+ #
231
+ # plsql.employees.delete(:employee_id => 1)
232
+ # # => DELETE FROM employees WHERE employee_id = 1
233
+ #
234
+ def delete(sql_params='', *bindvars)
235
+ delete_sql = "DELETE FROM \"#{@schema_name}\".\"#{@table_name}\" "
236
+ case sql_params
237
+ when String
238
+ delete_sql << sql_params
239
+ when Hash
240
+ raise ArgumentError, "Cannot specify bind variables when passing WHERE conditions as Hash" unless bindvars.empty?
241
+ where_sqls = []
242
+ sql_params.each do |k,v|
243
+ where_sqls << "#{k} = :#{k}"
244
+ bindvars << v
245
+ end
246
+ delete_sql << "WHERE " << where_sqls.join(' AND ') unless where_sqls.empty?
247
+ else
248
+ raise ArgumentError, "Only String or Hash can be provided as SQL condition argument"
249
+ end
250
+ @schema.execute(delete_sql, *bindvars)
251
+ end
252
+
253
+ # wrapper class to simulate Procedure class for ProcedureClass#exec
254
+ class TableProcedure #:nodoc:
255
+ attr_reader :arguments, :argument_list, :return, :out_list, :schema
256
+
257
+ def initialize(schema, table, operation)
258
+ @schema = schema
259
+ @table = table
260
+ @operation = operation
261
+
262
+ @return = [nil]
263
+ @out_list = [[]]
264
+
265
+ case @operation
266
+ when :insert
267
+ @argument_list = [[]]
268
+ @arguments = [{}]
269
+ @insert_columns = []
270
+ @insert_values = []
271
+ when :update
272
+ @argument_list = [[]]
273
+ @arguments = [{}]
274
+ @set_sqls = []
275
+ @set_values = []
276
+ @where_sqls = []
277
+ @where_values = []
278
+ end
279
+ end
280
+
281
+ def overloaded?
282
+ false
283
+ end
284
+
285
+ def procedure
286
+ nil
287
+ end
288
+
289
+ def add_insert_arguments(params)
290
+ params.each do |k,v|
291
+ raise ArgumentError, "Invalid column name #{k.inspect} specified as argument" unless (column_metadata = @table.columns[k])
292
+ @argument_list[0] << k
293
+ @arguments[0][k] = column_metadata
294
+ @insert_values << v
295
+ end
296
+ end
297
+
298
+ def add_set_arguments(params)
299
+ params.each do |k,v|
300
+ raise ArgumentError, "Invalid column name #{k.inspect} specified as argument" unless (column_metadata = @table.columns[k])
301
+ @argument_list[0] << k
302
+ @arguments[0][k] = column_metadata
303
+ @set_sqls << "#{k}=:#{k}"
304
+ @set_values << v
305
+ end
306
+ end
307
+
308
+ def add_where_arguments(params)
309
+ case params
310
+ when Hash
311
+ params.each do |k,v|
312
+ raise ArgumentError, "Invalid column name #{k.inspect} specified as argument" unless (column_metadata = @table.columns[k])
313
+ @argument_list[0] << :"w_#{k}"
314
+ @arguments[0][:"w_#{k}"] = column_metadata
315
+ @where_sqls << "#{k}=:w_#{k}"
316
+ @where_values << v
317
+ end
318
+ when String
319
+ @where_sqls << params
320
+ end
321
+ end
322
+
323
+ def argument_values
324
+ case @operation
325
+ when :insert
326
+ @insert_values
327
+ when :update
328
+ @set_values + @where_values
329
+ end
330
+ end
331
+
332
+ def call_sql(params_string)
333
+ case @operation
334
+ when :insert
335
+ "INSERT INTO \"#{@table.schema_name}\".\"#{@table.table_name}\"(#{@argument_list[0].map{|a| a.to_s}.join(', ')}) VALUES (#{params_string});\n"
336
+ when :update
337
+ update_sql = "UPDATE \"#{@table.schema_name}\".\"#{@table.table_name}\" SET #{@set_sqls.join(', ')}"
338
+ update_sql << " WHERE #{@where_sqls.join(' AND ')}" unless @where_sqls.empty?
339
+ update_sql << ";\n"
340
+ update_sql
341
+ end
342
+ end
343
+
344
+ end
345
+
346
+ end
347
+
348
+ end
@@ -0,0 +1,275 @@
1
+ module PLSQL
2
+
3
+ module TypeClassMethods #:nodoc:
4
+ def find(schema, type)
5
+ if schema.select_first(
6
+ "SELECT type_name FROM all_types
7
+ WHERE owner = :owner
8
+ AND type_name = :table_name",
9
+ schema.schema_name, type.to_s.upcase)
10
+ new(schema, type)
11
+ # search for synonym
12
+ elsif (row = schema.select_first(
13
+ "SELECT t.owner, t.type_name
14
+ FROM all_synonyms s, all_types t
15
+ WHERE s.owner = :owner
16
+ AND s.synonym_name = :synonym_name
17
+ AND t.owner = s.table_owner
18
+ AND t.type_name = s.table_name
19
+ UNION ALL
20
+ SELECT t.owner, t.type_name
21
+ FROM all_synonyms s, all_types t
22
+ WHERE s.owner = 'PUBLIC'
23
+ AND s.synonym_name = :synonym_name
24
+ AND t.owner = s.table_owner
25
+ AND t.type_name = s.table_name",
26
+ schema.schema_name, type.to_s.upcase, type.to_s.upcase))
27
+ new(schema, row[1], row[0])
28
+ else
29
+ nil
30
+ end
31
+ end
32
+ end
33
+
34
+ class Type
35
+ extend TypeClassMethods
36
+
37
+ attr_reader :typecode, :attributes, :schema_name, :type_name, :type_object_id #:nodoc:
38
+
39
+ def initialize(schema, type, override_schema_name = nil) #:nodoc:
40
+ @schema = schema
41
+ @schema_name = override_schema_name || schema.schema_name
42
+ @type_name = type.to_s.upcase
43
+ @attributes = {}
44
+ @type_procedures = {}
45
+
46
+ @typecode, @type_object_id = @schema.select_first(
47
+ "SELECT t.typecode, o.object_id FROM all_types t, all_objects o
48
+ WHERE t.owner = :owner
49
+ AND t.type_name = :type_name
50
+ AND o.owner = t.owner
51
+ AND o.object_name = t.type_name
52
+ AND o.object_type = 'TYPE'",
53
+ @schema_name, @type_name)
54
+
55
+ @schema.select_all(
56
+ "SELECT attr_name, attr_no,
57
+ attr_type_name, length, precision, scale,
58
+ attr_type_owner, attr_type_mod,
59
+ (SELECT t.typecode FROM all_types t
60
+ WHERE t.owner = attr_type_owner
61
+ AND t.type_name = attr_type_name) typecode
62
+ FROM all_type_attrs
63
+ WHERE owner = :owner
64
+ AND type_name = :type_name
65
+ ORDER BY attr_no",
66
+ @schema_name, @type_name
67
+ ) do |r|
68
+ attr_name, position,
69
+ data_type, data_length, data_precision, data_scale,
70
+ data_type_owner, data_type_mod, typecode = r
71
+ @attributes[attr_name.downcase.to_sym] = {
72
+ :position => position && position.to_i,
73
+ :data_type => data_type_owner && (typecode == 'COLLECTION' ? 'TABLE' : 'OBJECT' ) || data_type,
74
+ :data_length => data_type_owner ? nil : data_length && data_length.to_i,
75
+ :data_precision => data_precision && data_precision.to_i,
76
+ :data_scale => data_scale && data_scale.to_i,
77
+ :type_owner => data_type_owner,
78
+ :type_name => data_type_owner && data_type,
79
+ :sql_type_name => data_type_owner && "#{data_type_owner}.#{data_type}"
80
+ }
81
+ end
82
+ end
83
+
84
+ # is type collection?
85
+ def collection?
86
+ @is_collection ||= @typecode == 'COLLECTION'
87
+ end
88
+
89
+ # list of object type attribute names
90
+ def attribute_names
91
+ @attribute_names ||= @attributes.keys.sort_by{|k| @attributes[k][:position]}
92
+ end
93
+
94
+ # create new PL/SQL object instance
95
+ def new(*args, &block)
96
+ procedure = find_procedure(:new)
97
+ # in case of collections pass array of elements as one argument for constructor
98
+ if collection? && !(args.size == 1 && args[0].is_a?(Array))
99
+ args = [args]
100
+ end
101
+ result = procedure.exec_with_options(args, {:skip_self => true}, &block)
102
+ # TODO: collection constructor should return Array of ObhjectInstance objects
103
+ if collection?
104
+ result
105
+ else
106
+ # TODO: what to do if block is passed to constructor?
107
+ ObjectInstance.create(self, result)
108
+ end
109
+ end
110
+
111
+ def method_missing(method, *args, &block) #:nodoc:
112
+ if procedure = find_procedure(method)
113
+ procedure.exec_with_options(args, {}, &block)
114
+ else
115
+ raise ArgumentError, "No PL/SQL procedure '#{method.to_s.upcase}' found for type '#{@type_name}'"
116
+ end
117
+ end
118
+
119
+ def find_procedure(new_or_procedure) #:nodoc:
120
+ @type_procedures[new_or_procedure] ||= begin
121
+ procedure_name = new_or_procedure == :new ? @type_name : new_or_procedure
122
+ # find defined procedure for type
123
+ if @schema.select_first(
124
+ "SELECT procedure_name FROM all_procedures
125
+ WHERE owner = :owner
126
+ AND object_name = :object_name
127
+ AND procedure_name = :procedure_name",
128
+ @schema_name, @type_name, procedure_name.to_s.upcase)
129
+ TypeProcedure.new(@schema, self, procedure_name)
130
+ # call default constructor
131
+ elsif new_or_procedure == :new
132
+ TypeProcedure.new(@schema, self, :new)
133
+ end
134
+ end
135
+ end
136
+
137
+ # wrapper class to simulate Procedure class for ProcedureClass#exec
138
+ class TypeProcedure #:nodoc:
139
+ include ProcedureCommon
140
+
141
+ def initialize(schema, type, procedure)
142
+ @schema = schema
143
+ @type = type
144
+ @schema_name = @type.schema_name
145
+ @type_name = @type.type_name
146
+ @object_id = @type.type_object_id
147
+
148
+ # if default constructor
149
+ if @default_constructor = (procedure == :new)
150
+ @procedure = @type.collection? ? nil : @type_name
151
+ set_default_constructor_arguments
152
+ # if defined type procedure
153
+ else
154
+ @procedure = procedure.to_s.upcase
155
+ get_argument_metadata
156
+ # add also definition for default constructor in case of custom constructor
157
+ set_default_constructor_arguments if @procedure == @type_name
158
+ end
159
+
160
+ # constructors do not need type prefix in call
161
+ @package = @procedure == @type_name ? nil : @type_name
162
+ end
163
+
164
+ # will be called for collection constructor
165
+ def call_sql(params_string)
166
+ "#{params_string};\n"
167
+ end
168
+
169
+ attr_reader :arguments, :argument_list, :out_list
170
+ def arguments_without_self
171
+ @arguments_without_self ||= begin
172
+ hash = {}
173
+ @arguments.each do |ov, args|
174
+ hash[ov] = args.reject{|key, value| key == :self}
175
+ end
176
+ hash
177
+ end
178
+ end
179
+
180
+ def argument_list_without_self
181
+ @argument_list_without_self ||= begin
182
+ hash = {}
183
+ @argument_list.each do |ov, arg_list|
184
+ hash[ov] = arg_list.select{|arg| arg != :self}
185
+ end
186
+ hash
187
+ end
188
+ end
189
+
190
+ def out_list_without_self
191
+ @out_list_without_self ||= begin
192
+ hash = {}
193
+ @out_list.each do |ov, out_list|
194
+ hash[ov] = out_list.select{|arg| arg != :self}
195
+ end
196
+ hash
197
+ end
198
+ end
199
+
200
+ def exec_with_options(args, options={}, &block)
201
+ call = ProcedureCall.new(self, args, options)
202
+ result = call.exec(&block)
203
+ # if procedure was called then modified object is returned in SELF output parameter
204
+ if result.is_a?(Hash) && result[:self]
205
+ object = result.delete(:self)
206
+ result.empty? ? object : [object, result]
207
+ else
208
+ result
209
+ end
210
+ end
211
+
212
+ private
213
+
214
+ def set_default_constructor_arguments
215
+ @arguments ||= {}
216
+ @argument_list ||= {}
217
+ @out_list ||= {}
218
+ @return ||= {}
219
+ # either this will be the only overload or it will be additional
220
+ overload = @arguments.keys.size
221
+ # if type is collection then expect array of objects as argument
222
+ if @type.collection?
223
+ @arguments[overload] = {
224
+ :value => {
225
+ :position => 1,
226
+ :data_type => 'TABLE',
227
+ :in_out => 'IN',
228
+ :type_owner => @schema_name,
229
+ :type_name => @type_name,
230
+ :sql_type_name => "#{@schema_name}.#{@type_name}"
231
+ }
232
+ }
233
+ # otherwise if type is object type then expect object attributes as argument list
234
+ else
235
+ @arguments[overload] = @type.attributes
236
+ end
237
+ attributes = @arguments[overload]
238
+ @argument_list[overload] = attributes.keys.sort {|k1, k2| attributes[k1][:position] <=> attributes[k2][:position]}
239
+ # returns object or collection
240
+ @return[overload] = {
241
+ :position => 0,
242
+ :data_type => @type.collection? ? 'TABLE' : 'OBJECT',
243
+ :in_out => 'OUT',
244
+ :type_owner => @schema_name,
245
+ :type_name => @type_name,
246
+ :sql_type_name => "#{@schema_name}.#{@type_name}"
247
+ }
248
+ @out_list[overload] = []
249
+ @overloaded = overload > 0
250
+ end
251
+
252
+ end
253
+
254
+ end
255
+
256
+ class ObjectInstance < Hash #:nodoc:
257
+ attr_accessor :plsql_type
258
+
259
+ def self.create(type, attributes)
260
+ object = self.new.merge!(attributes)
261
+ object.plsql_type = type
262
+ object
263
+ end
264
+
265
+ def method_missing(method, *args, &block)
266
+ if procedure = @plsql_type.find_procedure(method)
267
+ procedure.exec_with_options(args, :self => self, &block)
268
+ else
269
+ raise ArgumentError, "No PL/SQL procedure '#{method.to_s.upcase}' found for type '#{@plsql_type.type_name}' object"
270
+ end
271
+ end
272
+
273
+ end
274
+
275
+ end