simpleOracleJDBC 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -190,6 +190,180 @@ The best way to learn how to use Simple Oracle JDBC is to read through the sampl
190
190
 
191
191
  Basic Ruby types are mapped automatically into the correct types for JDBC interaction, and the JDBC types are mapped back to Ruby types when they are retrieved from the database.
192
192
 
193
+ # PLSQL Arrays
194
+
195
+ Passing arrays to and from PLSQL objects is somewhat painful. To do this, you need to create type objects on the database, eg:
196
+
197
+ create or replace type t_varchar_tab is table of varchar2(100);
198
+ /
199
+
200
+ Then you can define PLSQL functions to have input or output parameters of that type, eg:
201
+
202
+ create or replace function test_array_varchar(i_array t_varchar2_tab)
203
+ return t_varchar2_tab
204
+ is
205
+ v_return_value t_varchar2_tab;
206
+ begin
207
+ v_return_value := t_varchar2_tab();
208
+ for i in 1..i_array.count loop
209
+ v_return_value.extend(1);
210
+ v_return_value(v_return_value.count) := i_array(i);
211
+ end loop;
212
+ return v_return_value;
213
+ end;
214
+ /
215
+
216
+ Using the type and function defined above, you can pass an array to the function and receive the result using a similar interface as normal values:
217
+
218
+ call = conn.prepare_proc("begin
219
+ :out_array := test_array_varchar(:i_array);
220
+ end;")
221
+ call.execute([SimpleOracleJDBC::OraArray, SimpleOracleJDBC::OraArray.new('t_varchar2_tab', nil), :out],
222
+ SimpleOracleJDBC::OraArray.new('t_varchar2_tab', ['abc', 'def', nil]))
223
+ return_array = call[1]
224
+ return_array.each do |v|
225
+ puts "The value is: #{v}"
226
+ end
227
+
228
+ # The value is: abc
229
+ # The value is: def
230
+ # The value is:
231
+
232
+ There are a few important differences in the syntax for array calls.
233
+
234
+ To pass an array in, you must create a SimpleOracleJDBC::OraArray object. This takes 2 parameters:
235
+
236
+ 1. The name of the type on the Oracle database
237
+ 2. A Ruby array of values to pass to Oracle.
238
+
239
+ Right now, an array of Integers, Floats, String, Dates or Times is supported. Nil values are allowed (as shown in the example above).
240
+
241
+ To receive an array of values, again use the SimpleOracleJDBC::OraArray class. The syntax is similar to receiving any output variable from a stored procedure (ie the 3 element array syntax), except the 2nd element in the array is no longer nil. It is important to create an instance of the OraArray object using the name of the Oracle Type, as this is required to retrieve the results from the PLSQL call.
242
+
243
+ For in out parameters, simply use the 3 element array syntax as with out parameters, only pass a Ruby array as the second parameter.
244
+
245
+ The array feature has been tested with Oracle arrays of char, varchar2, integer, number, date, timestamp and raw.
246
+
247
+ # PLSQL Record Types
248
+
249
+ Using a similar interface as for PLSQL Arrays, it is possible to bind PLSQL Record types to a stored procedure. A PLSQL record type is define on the database using something like the following:
250
+
251
+ create or replace type t_record as object (
252
+ p_varchar varchar2(10),
253
+ p_integer integer,
254
+ p_number number,
255
+ p_char char(10),
256
+ p_date date,
257
+ p_timestamp timestamp,
258
+ p_raw raw(10)
259
+ );
260
+ /
261
+
262
+ create or replace function test_record(i_record t_record)
263
+ return t_record
264
+ is
265
+ begin
266
+ return i_record;
267
+ end;
268
+ /
269
+
270
+ Using the type and function above, it is possible to pass a Ruby Array of values for the record and receive a Ruby Array as a response:
271
+
272
+ call = conn.prepare_proc("begin
273
+ :out_array := test_record(:i_array);
274
+ end;")
275
+ record = ["The String", 123, 456.789, 'THE CHAR', Time.gm(2013,11,23), Time.gm(2013,12,23,12,24,36), 'ED12ED12']
276
+ call.execute([SimpleOracleJDBC::OraRecord, SimpleOracleJDBC::OraRecord.new('t_record', nil), :out],
277
+ SimpleOracleJDBC::OraRecord.new('t_record', record))
278
+ return_array = call[1]
279
+ return_array.each do |v|
280
+ puts "The value is: #{v}"
281
+ end
282
+
283
+ # The value is: The String
284
+ # The value is: 123.0
285
+ # The value is: 456.789
286
+ # The value is: THE CHAR
287
+ # The value is: 2013-11-23 00:00:00 +0000
288
+ # The value is: 2013-12-23 12:24:36 +0000
289
+ # The value is: ED12ED12
290
+
291
+ There are a few important differences in the syntax for record calls.
292
+
293
+ To pass a record in, you must create a SimpleOracleJDBC::OraRecord object. This takes 2 parameters:
294
+
295
+ 1. The name of the type on the Oracle database
296
+ 2. A Ruby array of values to pass to Oracle.
297
+
298
+ The array of Ruby values must contain the same number of fields in the record. To set a field in the record to null pass nil inside the array.
299
+
300
+ To receive a record from a procedure call, again use the SimpleOracleJDBC::OraRecord class. The syntax is similar to receiving any output variable from a stored procedure (ie the 3 element array syntax), except the 2nd element in the array is no longer nil. It is important to create an instance of the OraRecord object using the name of the Oracle Type, as this is required to retrieve the results from the PLSQL call.
301
+
302
+ For in out parameters, simply use the 3 element array syntax as with out parameters, only pass a Ruby array as the second parameter.
303
+
304
+ # Arrays of PLSQL Records
305
+
306
+ If you define a PLSQL array as table of a record type, then you have an array of PLSQL records. For example, building on the record type created above:
307
+
308
+ create or replace type t_record_tab as table of t_record;
309
+ /
310
+
311
+ create or replace function test_array_of_records(i_array t_record_tab)
312
+ return t_record_tab
313
+ is
314
+ v_return_value t_record_tab;
315
+ begin
316
+ v_return_value := t_record_tab();
317
+ for i in 1..i_array.count loop
318
+ v_return_value.extend(1);
319
+ v_return_value(v_return_value.count) := i_array(i);
320
+ end loop;
321
+ return v_return_value;
322
+ end;
323
+ /
324
+
325
+ Binding an array like this to a stored procedure works just like binding an array of values. The only difference is that each value passed in the input array, must also be an array. Each of those arrays are then converted into the internal Oracle format using the OraRecord class. For example:
326
+
327
+ call = conn.prepare_proc("begin
328
+ :out_array := test_array_of_records(:i_array);
329
+ end;")
330
+ record = ["The String", 123, 456.789, 'THE CHAR', Time.gm(2013,11,23), Time.gm(2013,12,23,12,24,36), 'ED12ED12']
331
+ call.execute([SimpleOracleJDBC::OraArray, SimpleOracleJDBC::OraArray.new('t_record_tab', nil), :out],
332
+ # ! Note how the record is an array inside of an array
333
+ SimpleOracleJDBC::OraArray.new('t_record_tab', [record]))
334
+ return_array = call[1]
335
+ return_array.each do |v|
336
+ # Each return element in the array is an array
337
+ puts "The value is: #{v[0]}"
338
+ end
339
+
340
+ # The value is: The String
341
+
342
+
343
+ # What About Nested Types?
344
+
345
+ A nested type is a type that has another type as one of its attributes. Right now they are not supported.
346
+
347
+ # SQL Arrays
348
+
349
+ Similar to PLSQL arrays, it is possible to bind an array of values to an SQL call:
350
+
351
+ sql = @interface.execute_sql("select * from table(:b_tab)",
352
+ SimpleOracleJDBC::OraArray.new('t_varchar2_tab', ['abc', 'def']))
353
+
354
+ Again, instead of passing a simple Ruby type, you need to pass an instance of OraArray as the bind variable.
355
+
356
+ This also works with PLSQL Arrays of Records, eg:
357
+
358
+ sql = @interface.execute_sql("select * from table(:b_tab)",
359
+ SimpleOracleJDBC::OraArray.new('t_record_tab', [
360
+ ["S1", nil, nil, nil, nil, nil, nil],
361
+ ["S2", nil, nil, nil, nil, nil, nil] ]))
362
+
363
+
364
+ More complex types are not yet supported.
365
+
366
+
193
367
  # TODO (AKA missing features)
194
368
 
195
369
  Bindable types that are not yet supported:
@@ -199,7 +373,7 @@ Bindable types that are not yet supported:
199
373
 
200
374
  Types that cannot be retrieved from an SQL result set
201
375
 
202
- * CLOB
376
+ * CLOB - is returned as a string so long as it is under 4000 characters
203
377
  * Cursor
204
378
  * Long
205
379
  * nvarchar etc
@@ -9,11 +9,19 @@ module SimpleOracleJDBC
9
9
  java_import 'oracle.jdbc.OracleTypes'
10
10
  java_import 'java.sql.DriverManager'
11
11
  java_import 'java.sql.SQLException'
12
+ java_import 'oracle.sql.ArrayDescriptor'
13
+ java_import 'oracle.sql.ARRAY'
14
+ java_import 'oracle.sql.StructDescriptor'
15
+ java_import 'oracle.sql.STRUCT'
16
+
12
17
  end
13
18
 
19
+ require 'simple_oracle_jdbc/type_map'
14
20
  require 'simple_oracle_jdbc/bindings'
15
21
  require 'simple_oracle_jdbc/result_set'
16
22
  #require 'interface'
17
23
  require 'simple_oracle_jdbc/sql'
18
24
  require 'simple_oracle_jdbc/interface'
19
25
  require 'simple_oracle_jdbc/db_call'
26
+ require 'simple_oracle_jdbc/ora_array'
27
+ require 'simple_oracle_jdbc/ora_record'
@@ -1,6 +1,8 @@
1
1
  module SimpleOracleJDBC
2
2
  module Binding
3
3
 
4
+ include SimpleOracleJDBC::TypeMap
5
+
4
6
  # Provides a set of methods to map Ruby types to their JDBC equivalent and back again.
5
7
 
6
8
 
@@ -64,7 +66,7 @@ module SimpleOracleJDBC
64
66
  value = v[1]
65
67
 
66
68
  if v.length == 3
67
- bind_out_parameter(obj, i, type)
69
+ bind_out_parameter(obj, i, type, value)
68
70
  end
69
71
  end
70
72
 
@@ -82,6 +84,10 @@ module SimpleOracleJDBC
82
84
  bind_refcursor(obj, value, i)
83
85
  elsif type == :raw
84
86
  bind_raw(obj, value, i)
87
+ elsif type == SimpleOracleJDBC::OraArray
88
+ value.bind_to_call(@connection, obj, i)
89
+ elsif type == SimpleOracleJDBC::OraRecord
90
+ value.bind_to_call(@connection, obj, i)
85
91
  else
86
92
  raise UnknownBindType, type.to_s
87
93
  end
@@ -113,16 +119,19 @@ module SimpleOracleJDBC
113
119
  end
114
120
 
115
121
  # :nodoc:
116
- def bind_out_parameter(obj, index, type)
117
- internal_type = RUBY_TO_JDBC_TYPES[type] || OracleTypes::VARCHAR
118
- obj.register_out_parameter(index, internal_type)
122
+ def bind_out_parameter(obj, index, type, value)
123
+ if type == SimpleOracleJDBC::OraArray or type == SimpleOracleJDBC::OraRecord
124
+ value.register_as_out_parameter(@connection, obj, index)
125
+ else
126
+ internal_type = RUBY_TO_JDBC_TYPES[type] || OracleTypes::VARCHAR
127
+ obj.register_out_parameter(index, internal_type)
128
+ end
119
129
  end
120
130
 
121
131
  def bind_date(obj, v, i)
122
132
  if v
123
133
  # %Q is micro seconds since epoch. Divide by 1000 to get milli-sec
124
- jdbc_date = Java::JavaSql::Date.new(v.strftime("%s").to_f * 1000)
125
- obj.set_date(i, jdbc_date)
134
+ obj.set_date(i, ruby_date_as_jdbc_date(v))
126
135
  else
127
136
  obj.set_null(i, OracleTypes::DATE)
128
137
  end
@@ -132,8 +141,7 @@ module SimpleOracleJDBC
132
141
  if v
133
142
  # Need to use an Oracle TIMESTAMP - dates don't allow a time to be specified
134
143
  # for some reason, even though a date in Oracle contains a time.
135
- jdbc_time = TIMESTAMP.new(Java::JavaSql::Timestamp.new(v.to_f * 1000))
136
- obj.setTIMESTAMP(i, jdbc_time)
144
+ obj.setTIMESTAMP(i, ruby_time_as_jdbc_timestamp(v))
137
145
  else
138
146
  obj.set_null(i, OracleTypes::TIMESTAMP)
139
147
  end
@@ -157,13 +165,7 @@ module SimpleOracleJDBC
157
165
 
158
166
  def bind_number(obj, v, i)
159
167
  if v
160
- # Avoid warning that appeared in JRuby 1.7.3. There are many signatures of
161
- # Java::OracleSql::NUMBER and it has to pick one. This causes a warning. This
162
- # technique works around the warning and forces it to the the signiture with a
163
- # double input - see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby
164
- # under the Constructors section.
165
- construct = Java::OracleSql::NUMBER.java_class.constructor(Java::double)
166
- obj.set_number(i, construct.new_instance(v))
168
+ obj.set_number(i, ruby_number_as_jdbc_number(v))
167
169
  else
168
170
  obj.set_null(i, OracleTypes::NUMBER)
169
171
  end
@@ -177,30 +179,20 @@ module SimpleOracleJDBC
177
179
 
178
180
  def bind_raw(obj, v, i)
179
181
  if v
180
- raw = Java::OracleSql::RAW.new(v)
181
- obj.set_raw(i, raw)
182
+ obj.set_raw(i, ruby_raw_string_as_jdbc_raw(v))
182
183
  else
183
184
  obj.set_null(i, OracleTypes::RAW)
184
185
  end
185
186
  end
186
187
 
187
-
188
188
  def retrieve_date(obj, i)
189
189
  jdate = obj.get_date(i)
190
- if jdate
191
- Date.new(jdate.get_year+1900, jdate.get_month+1, jdate.get_date)
192
- else
193
- nil
194
- end
190
+ java_date_as_date(jdate)
195
191
  end
196
192
 
197
193
  def retrieve_time(obj, i)
198
194
  jdate = obj.get_timestamp(i)
199
- if jdate
200
- Time.at(jdate.get_time.to_f / 1000)
201
- else
202
- nil
203
- end
195
+ java_date_as_time(jdate)
204
196
  end
205
197
 
206
198
  def retrieve_string(obj, i)
@@ -210,35 +202,27 @@ module SimpleOracleJDBC
210
202
  def retrieve_int(obj, i)
211
203
  v = obj.get_int(i)
212
204
  if obj.was_null
213
- nil
214
- else
215
- v
205
+ v = nil
216
206
  end
207
+ java_integer_as_integer(v)
217
208
  end
218
209
 
219
210
  def retrieve_number(obj, i)
220
211
  v = obj.get_number(i)
221
- if v
222
- v.double_value
223
- else
224
- nil
225
- end
212
+ java_number_as_float(v)
226
213
  end
227
214
 
228
215
  def retrieve_refcursor(obj, i)
229
216
  rset = obj.get_object(i)
230
- results = Sql.new
217
+ # Dummy connection passed as it is never needed?
218
+ results = Sql.new(nil)
231
219
  results.result_set = rset
232
220
  results
233
221
  end
234
222
 
235
223
  def retrieve_raw(obj, i)
236
224
  v = obj.get_raw(i)
237
- if v
238
- v.string_value
239
- else
240
- nil
241
- end
225
+ oracle_raw_as_string(v)
242
226
  end
243
227
 
244
228
  end
@@ -104,6 +104,8 @@ module SimpleOracleJDBC
104
104
  retrieve_refcursor(@call, i)
105
105
  elsif bind[0] == :raw
106
106
  retrieve_raw(@call, i)
107
+ elsif bind[0] == SimpleOracleJDBC::OraArray or bind[0] == SimpleOracleJDBC::OraRecord
108
+ bind[1].retrieve_out_value(@connection, @call, i)
107
109
  end
108
110
  else
109
111
  # If its not an array, it was just an IN, so just pull the bind
@@ -0,0 +1,133 @@
1
+ module SimpleOracleJDBC
2
+
3
+ class OraArray
4
+ include TypeMap
5
+
6
+ attr_reader :ora_type
7
+
8
+ # This must be initialized with the name of the Oracle array type as defined
9
+ # on the database, ie the t_name is table of varchar2(10);
10
+ #
11
+ # Values must be an array of Ruby objects, or nil. The values in the array
12
+ # will be cast into the appropriate Oracle type depending on the definition
13
+ # of the array defined in Oracle.
14
+ def initialize(ora_type, values)
15
+ @ora_type = ora_type.upcase
16
+ self.values = values
17
+ @descriptor = nil
18
+ end
19
+
20
+ # Values must be a Ruby array of objects or nil.
21
+ #
22
+ # While the values can be set in upon object initialization, this method
23
+ # allows them to be changed. The one advantage is that it allows the
24
+ # array descriptor to be reused across many database calls. As this must
25
+ # be queried from the database, it requires 1 database round trip for each new object,
26
+ # but is cached inside the object once it is initialized.
27
+ def values=(value_array)
28
+ if value_array and !value_array.is_a? Array
29
+ raise "The values must be a Ruby array, not #{value_array.class}"
30
+ end
31
+ @values = value_array || Array.new
32
+ end
33
+
34
+ # Given a database connection, a prepared statement and a bind index,
35
+ # this method will bind the array of values (set at object initialization time
36
+ # or by the values= method) to the statement.
37
+ def bind_to_call(conn, stmt, index)
38
+ # First thing that is need is a descriptor for the given type
39
+ set_descriptor(conn)
40
+ base_type = @descriptor.get_base_name
41
+
42
+ jarray = nil
43
+ if base_type == 'VARCHAR' or base_type == 'CHAR'
44
+ jarray = @values.to_java
45
+ elsif base_type == 'RAW'
46
+ jarray = @values.map{|i| ruby_raw_string_as_jdbc_raw(i) }.to_java
47
+ elsif base_type == 'NUMBER' or base_type == 'INTEGER'
48
+ jarray = @values.map{|i| ruby_number_as_jdbc_number(i) }.to_java
49
+ elsif base_type == 'DATE' or base_type == 'TIMESTAMP'
50
+ jarray = @values.map{|i| ruby_any_date_as_jdbc_date(i) }.to_java
51
+ elsif @values.first.is_a? Array or @values.first.nil?
52
+ # If the value is an array, assume we are dealing with an array
53
+ # of records. Also need the nil check as we could be binding
54
+ # an empty array or a return value.
55
+ jarray = create_struct_array(conn, base_type,@values)
56
+ else
57
+ raise "#{base_type}: Unimplemented Array Type"
58
+ end
59
+ ora_array = ARRAY.new(@descriptor, conn, jarray)
60
+ stmt.set_object(index, ora_array)
61
+ end
62
+
63
+
64
+ # Given a database connection, a prepared statement and a bind index,
65
+ # register the bind at that index as an out or inout parameter.
66
+ def register_as_out_parameter(conn, stmt, index)
67
+ set_descriptor(conn)
68
+ stmt.register_out_parameter(index, OracleTypes::ARRAY, @ora_type)
69
+ end
70
+
71
+ # After executing a statement, retrieve the resultant array from Oracle
72
+ # returning a Ruby array of Ruby objects.
73
+ def retrieve_out_value(conn, stmt, index)
74
+ set_descriptor(conn)
75
+ ora_array = stmt.get_array(index)
76
+ base_type = ora_array.get_base_type_name
77
+ if base_type == 'VARCHAR' or base_type == 'CHAR'
78
+ retrieve_as_string(ora_array)
79
+ elsif base_type == 'RAW'
80
+ retrieve_as_raw(ora_array)
81
+ elsif base_type == 'NUMBER' or base_type == 'INTEGER'
82
+ retrieve_as_number(ora_array)
83
+ elsif base_type == 'DATE' or base_type == 'TIMESTAMP'
84
+ retrieve_as_date(ora_array)
85
+ else
86
+ retrieve_oracle_record(conn, base_type, ora_array)
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def set_descriptor(conn)
93
+ @descriptor ||= ArrayDescriptor.createDescriptor(@ora_type, conn);
94
+ end
95
+
96
+ def retrieve_as_string(ora_array)
97
+ ora_array.get_array.to_a
98
+ end
99
+
100
+ def retrieve_as_number(ora_array)
101
+ ora_array.get_array.to_a.map{|v| java_number_as_float(v) }
102
+ end
103
+
104
+ def retrieve_as_raw(ora_array)
105
+ ora_array.get_oracle_array.to_a.map{|v| oracle_raw_as_string(v) }
106
+ end
107
+
108
+ # Always returns dates are Ruby Time objects
109
+ def retrieve_as_date(ora_array)
110
+ ora_array.get_array.to_a.map{|v| java_date_as_time(v) }
111
+ end
112
+
113
+ def retrieve_oracle_record(conn, type, ora_array)
114
+ ora_record = OraRecord.new(type,nil)
115
+ ora_array.get_array.to_a.map{|r| ora_record.convert_struct_to_ruby(conn, r) }
116
+ end
117
+
118
+ # Converts an array of arrays into their intended Oracle
119
+ # record type
120
+ def create_struct_array(conn, type, values)
121
+ ora_record = OraRecord.new(type, nil)
122
+ array_of_structs = Array.new
123
+ values.each do |v|
124
+ array_of_structs.push ora_record.convert_to_oracle_struct(conn, v)
125
+ end
126
+ array_of_structs.to_java
127
+ end
128
+
129
+
130
+ end
131
+
132
+ end
133
+