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.
- checksums.yaml +15 -0
- data/Gemfile +14 -0
- data/History.txt +172 -0
- data/License.txt +20 -0
- data/README.md +182 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/plsql/connection.rb +233 -0
- data/lib/plsql/helpers.rb +9 -0
- data/lib/plsql/jdbc_connection.rb +542 -0
- data/lib/plsql/oci8_patches.rb +25 -0
- data/lib/plsql/oci_connection.rb +339 -0
- data/lib/plsql/package.rb +80 -0
- data/lib/plsql/procedure.rb +269 -0
- data/lib/plsql/procedure_call.rb +124 -0
- data/lib/plsql/schema.rb +309 -0
- data/lib/plsql/sequence.rb +49 -0
- data/lib/plsql/sql_statements.rb +87 -0
- data/lib/plsql/table.rb +348 -0
- data/lib/plsql/type.rb +275 -0
- data/lib/plsql/variable.rb +146 -0
- data/lib/plsql/version.rb +3 -0
- data/lib/plsql/view.rb +41 -0
- data/lib/ruby-plsql.rb +1 -0
- data/lib/ruby_plsql.rb +18 -0
- data/ruby-plsql.gemspec +87 -0
- data/spec/plsql/connection_spec.rb +495 -0
- data/spec/plsql/package_spec.rb +149 -0
- data/spec/plsql/procedure_spec.rb +2048 -0
- data/spec/plsql/schema_spec.rb +331 -0
- data/spec/plsql/sequence_spec.rb +67 -0
- data/spec/plsql/sql_statements_spec.rb +91 -0
- data/spec/plsql/table_spec.rb +371 -0
- data/spec/plsql/type_spec.rb +304 -0
- data/spec/plsql/variable_spec.rb +505 -0
- data/spec/plsql/version_spec.rb +8 -0
- data/spec/plsql/view_spec.rb +264 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +79 -0
- metadata +159 -0
@@ -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
|
+
|
data/lib/plsql/table.rb
ADDED
@@ -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
|
data/lib/plsql/type.rb
ADDED
@@ -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
|