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