ruby-plsql 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,21 @@
1
+ == 0.3.0 2009-04-21
2
+
3
+ * New features
4
+ * Added Ruby 1.9.1 and ruby-oci8 2.x support
5
+ * Use plsql.activerecord_class = ActiveRecord::Base to simplify usage with Rails
6
+ * Improvements
7
+ * DATE to Time and DateTime conversion according to plsql.default_timezone (:local or :utc)
8
+ Use ActiveRecord::Base.default_timezone if plsql.activerecord_class=... is used
9
+ * Added BLOB data type support for input and output parameters and function return values
10
+ * Added support for private and public synonyms to functions/procedures and packages
11
+
1
12
  == 0.2.4 2009-03-06
13
+
2
14
  * Bug fixes
3
15
  * Fixed that procedures can be called with VARCHAR2 parameters with length up to 32767
4
16
 
5
17
  == 0.2.3 2008-10-17
18
+
6
19
  * Improvements
7
20
  * Added CLOB data type support for input and output parameters and function return values
8
21
  (both for MRI/OCI and JRuby/JDBC)
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Raimonds Simanovskis
1
+ Copyright (c) 2009 Raimonds Simanovskis
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.txt CHANGED
@@ -5,8 +5,8 @@
5
5
  == DESCRIPTION:
6
6
 
7
7
  ruby-plsql gem provides simple Ruby API for calling Oracle PL/SQL procedures.
8
- ruby-plsql support both MRI and JRuby runtime environments.
9
- This gem requires ruby-oci8 library (if MRI is used) or Oracle JDBC driver (ojdbc14.jar) (if JRuby is used) for connection to Oracle database.
8
+ ruby-plsql support both Ruby 1.8 MRI, Ruby 1.9.1 YARV and JRuby runtime environments.
9
+ This gem requires ruby-oci8 library version 1.x (if MRI is used) or 2.x (if Ruby 1.9.1 is used) or Oracle JDBC driver (ojdbc14.jar) (if JRuby is used) for connection to Oracle database.
10
10
 
11
11
  See http://blog.rayapps.com for more information.
12
12
 
@@ -14,7 +14,7 @@ Look ar RSpec tests under spec directory for usage examples.
14
14
 
15
15
  == FEATURES/PROBLEMS:
16
16
 
17
- * Currently just NUMBER, VARCHAR2, DATE, TIMESTAMP, CLOB argument types are supported for PL/SQL procedures
17
+ * Currently just NUMBER, VARCHAR2, DATE, TIMESTAMP, CLOB, BLOB argument types are supported for PL/SQL procedures
18
18
 
19
19
  == SYNOPSIS:
20
20
 
@@ -34,10 +34,19 @@ plsql.test_package.test_uppercase('xxx') # => 'XXX'
34
34
 
35
35
  plsql.logoff
36
36
 
37
+
38
+ If using with Rails then include in initializer file:
39
+
40
+ plsql.activerecord_class = ActiveRecord::Base
41
+
42
+ and then you do not need to specify plsql.connection (this is also safer when ActiveRecord reestablishes connection to database).
43
+
37
44
  == REQUIREMENTS:
38
45
 
39
- MRI
46
+ Ruby 1.8 MRI
40
47
  * Requires ruby-oci8 library to connect to Oracle (please use version 1.0.3 or later)
48
+ Ruby 1.9.1
49
+ * Requires ruby-oci8 library to connect to Oracle (please use version 2.0 or later)
41
50
  JRuby
42
51
  * Requires Oracle JDBC driver (ojdbc14.jar should be somewhere in PATH) to connect to Oracle
43
52
 
@@ -49,7 +58,7 @@ JRuby
49
58
 
50
59
  (The MIT License)
51
60
 
52
- Copyright (c) 2008 Raimonds Simanovskis
61
+ Copyright (c) 2009 Raimonds Simanovskis
53
62
 
54
63
  Permission is hereby granted, free of charge, to any person obtaining
55
64
  a copy of this software and associated documentation files (the
@@ -1,23 +1,37 @@
1
1
  module PLSQL
2
2
  class Connection
3
- attr_reader :raw_connection
4
3
  attr_reader :raw_driver
4
+ attr_reader :activerecord_class
5
5
 
6
- def initialize(raw_drv, raw_conn)
6
+ def initialize(raw_drv, raw_conn, ar_class = nil)
7
7
  @raw_driver = raw_drv
8
8
  @raw_connection = raw_conn
9
+ @activerecord_class = ar_class
9
10
  end
10
11
 
11
- def self.create(raw_conn)
12
- if !raw_conn.respond_to?(:java_class) && defined?(OCI8)
13
- OCIConnection.new(:oci, raw_conn)
14
- elsif raw_conn.respond_to?(:java_class) && raw_conn.java_class.to_s =~ /jdbc/
15
- JDBCConnection.new(:jdbc, raw_conn)
12
+ def self.create(raw_conn, ar_class = nil)
13
+ if ar_class && !(defined?(::ActiveRecord) && [ar_class, ar_class.superclass].include?(::ActiveRecord::Base))
14
+ raise ArgumentError, "Wrong ActiveRecord class"
15
+ end
16
+ # MRI 1.8.6 or YARV 1.9.1
17
+ if (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby") && defined?(OCI8)
18
+ OCIConnection.new(:oci, raw_conn, ar_class)
19
+ # JRuby
20
+ elsif (defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby")
21
+ JDBCConnection.new(:jdbc, raw_conn, ar_class)
16
22
  else
17
23
  raise ArgumentError, "Unknown raw driver"
18
24
  end
19
25
  end
20
26
 
27
+ def raw_connection
28
+ if @activerecord_class
29
+ @activerecord_class.connection.raw_connection
30
+ else
31
+ @raw_connection
32
+ end
33
+ end
34
+
21
35
  def oci?
22
36
  @raw_driver == :oci
23
37
  end
@@ -145,6 +145,8 @@ module PLSQL
145
145
  java.sql.Types::VARCHAR
146
146
  when 'Java::OracleSql::CLOB'
147
147
  Java::oracle.jdbc.OracleTypes::CLOB
148
+ when 'Java::OracleSql::BLOB'
149
+ Java::oracle.jdbc.OracleTypes::BLOB
148
150
  when 'Date'
149
151
  java.sql.Types::DATE
150
152
  when 'Time'
@@ -169,12 +171,10 @@ module PLSQL
169
171
  stmt.send("setString#{key && "AtName"}", key || i, value)
170
172
  when 'Java::OracleSql::CLOB'
171
173
  stmt.send("setClob#{key && "AtName"}", key || i, value)
172
- when 'Date'
173
- stmt.send("setDate#{key && "AtName"}", key || i, java.sql.Date.new(Time.parse(value.to_s).to_i*1000))
174
- when 'Time'
175
- stmt.send("setTime#{key && "AtName"}", key || i, java.sql.Time.new(value.to_i*1000))
176
- when 'DateTime'
177
- stmt.send("setTime#{key && "AtName"}", key || i, java.sql.Time.new(Time.parse(value.strftime("%c")).to_i*1000))
174
+ when 'Java::OracleSql::BLOB'
175
+ stmt.send("setBlob#{key && "AtName"}", key || i, value)
176
+ when 'Date', 'Time', 'DateTime'
177
+ stmt.send("setDATE#{key && "AtName"}", key || i, Java::oracle.sql.DATE.new(value.strftime("%Y-%m-%d %H:%M:%S")))
178
178
  when 'NilClass'
179
179
  stmt.send("setNull#{key && "AtName"}", key || i, get_java_sql_type(value, type))
180
180
  end
@@ -193,10 +193,16 @@ module PLSQL
193
193
  stmt.getString(i)
194
194
  when 'Java::OracleSql::CLOB'
195
195
  stmt.getClob(i)
196
+ when 'Java::OracleSql::BLOB'
197
+ stmt.getBlob(i)
196
198
  when 'Date','Time','DateTime'
197
- ts = stmt.getTimestamp(i)
198
- # ts && Time.parse(Time.at(ts.getTime/1000).iso8601)
199
- ts && Time.local(1900+ts.year, ts.month+1, ts.date, ts.hours, ts.minutes, ts.seconds)
199
+ if dt = stmt.getDATE(i)
200
+ d = dt.dateValue
201
+ t = dt.timeValue
202
+ Time.send(plsql.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
203
+ else
204
+ nil
205
+ end
200
206
  end
201
207
  end
202
208
 
@@ -206,17 +212,29 @@ module PLSQL
206
212
  rset.getString(i)
207
213
  when "CLOB"
208
214
  ora_value_to_ruby_value(rset.getClob(i))
215
+ when "BLOB"
216
+ ora_value_to_ruby_value(rset.getBlob(i))
209
217
  when "NUMBER"
210
218
  d = rset.getBigDecimal(i)
211
219
  if d.nil?
212
220
  nil
213
221
  elsif d.scale == 0
214
- d.longValue
222
+ d.toBigInteger+0
215
223
  else
216
- d.doubleValue
224
+ BigDecimal(d.toString)
217
225
  end
218
- when "DATE", "TIMESTAMP"
219
- Time.at(rset.getTimestamp(i).getTime/1000)
226
+ when "DATE"
227
+ if dt = rset.getDATE(i)
228
+ d = dt.dateValue
229
+ t = dt.timeValue
230
+ Time.send(plsql.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
231
+ else
232
+ nil
233
+ end
234
+ when /^TIMESTAMP/
235
+ ts = rset.getTimestamp(i)
236
+ ts && Time.send(Base.default_timezone, ts.year + 1900, ts.month + 1, ts.date, ts.hours, ts.minutes, ts.seconds,
237
+ ts.nanos / 1000)
220
238
  else
221
239
  nil
222
240
  end
@@ -228,14 +246,14 @@ module PLSQL
228
246
  [String, data_length || 32767]
229
247
  when "CLOB"
230
248
  [Java::OracleSql::CLOB, nil]
249
+ when "BLOB"
250
+ [Java::OracleSql::BLOB, nil]
231
251
  when "NUMBER"
232
252
  [BigDecimal, nil]
233
253
  when "DATE"
234
254
  [Time, nil]
235
255
  when "TIMESTAMP"
236
256
  [Time, nil]
237
- # CLOB
238
- # BLOB
239
257
  else
240
258
  [String, 32767]
241
259
  end
@@ -243,9 +261,16 @@ module PLSQL
243
261
 
244
262
  def ruby_value_to_ora_value(val, type)
245
263
  if type == BigDecimal
246
- val.nil? || val.is_a?(Fixnum) ? val : val.to_f
264
+ val.nil? || val.is_a?(Fixnum) || val.is_a?(BigDecimal) ? val : BigDecimal(val.to_s)
247
265
  elsif type == Time
248
- date_to_time(val)
266
+ case val
267
+ when DateTime
268
+ Time.send(plsql.default_timezone, val.year, val.month, val.day, val.hour, val.min, val.sec)
269
+ when Date
270
+ Time.send(plsql.default_timezone, val.year, val.month, val.day, 0, 0, 0)
271
+ else
272
+ val
273
+ end
249
274
  elsif type == Java::OracleSql::CLOB
250
275
  if val
251
276
  clob = Java::OracleSql::CLOB.createTemporary(raw_connection, false, Java::OracleSql::CLOB::DURATION_SESSION)
@@ -254,6 +279,14 @@ module PLSQL
254
279
  else
255
280
  Java::OracleSql::CLOB.getEmptyCLOB
256
281
  end
282
+ elsif type == Java::OracleSql::BLOB
283
+ if val
284
+ blob = Java::OracleSql::BLOB.createTemporary(raw_connection, false, Java::OracleSql::BLOB::DURATION_SESSION)
285
+ blob.setBytes(1, val.to_java_bytes)
286
+ blob
287
+ else
288
+ Java::OracleSql::BLOB.getEmptyBLOB
289
+ end
257
290
  else
258
291
  val
259
292
  end
@@ -263,14 +296,18 @@ module PLSQL
263
296
  case val
264
297
  when Float, BigDecimal
265
298
  ora_number_to_ruby_number(val)
266
- # when OraDate
267
- # ora_date_to_ruby_date(val)
268
299
  when Java::OracleSql::CLOB
269
300
  if val.isEmptyLob
270
301
  nil
271
302
  else
272
303
  val.getSubString(1, val.length)
273
304
  end
305
+ when Java::OracleSql::BLOB
306
+ if val.isEmptyLob
307
+ nil
308
+ else
309
+ String.from_java_bytes(val.getBytes(1, val.length))
310
+ end
274
311
  else
275
312
  val
276
313
  end
@@ -279,24 +316,11 @@ module PLSQL
279
316
  private
280
317
 
281
318
  def ora_number_to_ruby_number(num)
282
- num.to_i == num.to_f ? num.to_i : num.to_f
319
+ # return BigDecimal instead of Float to avoid rounding errors
320
+ # num.to_i == num.to_f ? num.to_i : num.to_f
321
+ num == (num_to_i = num.to_i) ? num_to_i : BigDecimal.new(num.to_s)
283
322
  end
284
323
 
285
- # def ora_date_to_ruby_date(val)
286
- # val.to_time
287
- # end
288
-
289
- def date_to_time(val)
290
- case val
291
- when Time
292
- val
293
- when DateTime
294
- Time.parse(val.strftime("%c"))
295
- when Date
296
- Time.parse(val.strftime("%c"))
297
- end
298
- end
299
-
300
324
  end
301
325
 
302
326
  end
@@ -91,6 +91,8 @@ module PLSQL
91
91
  [String, data_length || 32767]
92
92
  when "CLOB"
93
93
  [OCI8::CLOB, nil]
94
+ when "BLOB"
95
+ [OCI8::BLOB, nil]
94
96
  when "NUMBER"
95
97
  [OraNumber, nil]
96
98
  when "DATE"
@@ -106,13 +108,34 @@ module PLSQL
106
108
 
107
109
  def ruby_value_to_ora_value(val, type)
108
110
  if type == OraNumber
109
- val.nil? || val.is_a?(Fixnum) ? val : val.to_f
111
+ # pass parameters as OraNumber to avoid rounding errors
112
+ case val
113
+ when Bignum
114
+ OraNumber.new(val.to_s)
115
+ when BigDecimal
116
+ OraNumber.new(val.to_s('F'))
117
+ else
118
+ val
119
+ end
110
120
  elsif type == DateTime
111
- val ? val.to_datetime : nil
121
+ case val
122
+ when Time
123
+ ::DateTime.civil(val.year, val.month, val.day, val.hour, val.min, val.sec, Rational(val.utc_offset, 86400))
124
+ when DateTime
125
+ val
126
+ when Date
127
+ ::DateTime.civil(val.year, val.month, val.day, 0, 0, 0, 0)
128
+ else
129
+ val
130
+ end
112
131
  elsif type == OCI8::CLOB
113
132
  # ruby-oci8 cannot create CLOB from ''
114
133
  val = nil if val == ''
115
134
  OCI8::CLOB.new(raw_oci_connection, val)
135
+ elsif type == OCI8::BLOB
136
+ # ruby-oci8 cannot create BLOB from ''
137
+ val = nil if val == ''
138
+ OCI8::BLOB.new(raw_oci_connection, val)
116
139
  else
117
140
  val
118
141
  end
@@ -124,9 +147,13 @@ module PLSQL
124
147
  ora_number_to_ruby_number(val)
125
148
  when DateTime, OraDate
126
149
  ora_date_to_ruby_date(val)
127
- when OCI8::CLOB
128
- val.rewind
129
- val.read
150
+ when OCI8::LOB
151
+ if val.available?
152
+ val.rewind
153
+ val.read
154
+ else
155
+ nil
156
+ end
130
157
  else
131
158
  val
132
159
  end
@@ -146,15 +173,29 @@ module PLSQL
146
173
  end
147
174
 
148
175
  def ora_number_to_ruby_number(num)
149
- num.to_i == num.to_f ? num.to_i : num.to_f
176
+ # return BigDecimal instead of Float to avoid rounding errors
177
+ # num.to_i == num.to_f ? num.to_i : num.to_f
178
+ num == (num_to_i = num.to_i) ? num_to_i : BigDecimal.new(num.to_s)
150
179
  end
151
180
 
152
181
  def ora_date_to_ruby_date(val)
153
182
  case val
154
183
  when DateTime
155
- Time.parse(val.strftime("%c")) rescue val
184
+ # similar implementation as in oracle_enhanced adapter
185
+ begin
186
+ Time.send(plsql.default_timezone, val.year, val.month, val.day, val.hour, val.min, val.sec)
187
+ rescue
188
+ offset = plsql.default_timezone.to_sym == :local ? plsql.local_timezone_offset : 0
189
+ DateTime.civil(val.year, val.month, val.day, val.hour, val.min, val.sec, offset)
190
+ end
156
191
  when OraDate
157
- val.to_time rescue val.to_datetime
192
+ # similar implementation as in oracle_enhanced adapter
193
+ begin
194
+ Time.send(plsql.default_timezone, val.year, val.month, val.day, val.hour, val.minute, val.second)
195
+ rescue
196
+ offset = plsql.default_timezone.to_sym == :local ? plsql.local_timezone_offset : 0
197
+ DateTime.civil(val.year, val.month, val.day, val.hour, val.minute, val.second, offset)
198
+ end
158
199
  else
159
200
  val
160
201
  end
@@ -9,6 +9,18 @@ module PLSQL
9
9
  AND object_type = 'PACKAGE'",
10
10
  schema.schema_name, package.to_s.upcase)
11
11
  new(schema, package)
12
+ # search for synonym
13
+ elsif (row = schema.select_first("
14
+ SELECT o.owner, o.object_name
15
+ FROM all_synonyms s, all_objects o
16
+ WHERE s.owner IN (:owner, 'PUBLIC')
17
+ AND s.synonym_name = :synonym_name
18
+ AND o.owner = s.table_owner
19
+ AND o.object_name = s.table_name
20
+ AND o.object_type = 'PACKAGE'
21
+ ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)",
22
+ schema.schema_name, package.to_s.upcase))
23
+ new(schema, row[1], row[0])
12
24
  else
13
25
  nil
14
26
  end
@@ -18,8 +30,9 @@ module PLSQL
18
30
  class Package
19
31
  extend PackageClassMethods
20
32
 
21
- def initialize(schema, package)
33
+ def initialize(schema, package, override_schema_name = nil)
22
34
  @schema = schema
35
+ @override_schema_name = override_schema_name
23
36
  @package = package.to_s.upcase
24
37
  @procedures = {}
25
38
  end
@@ -29,7 +42,7 @@ module PLSQL
29
42
  def method_missing(method, *args)
30
43
  if procedure = @procedures[method]
31
44
  procedure.exec(*args)
32
- elsif procedure = Procedure.find(@schema, method, @package)
45
+ elsif procedure = Procedure.find(@schema, method, @package, @override_schema_name)
33
46
  @procedures[method] = procedure
34
47
  procedure.exec(*args)
35
48
  else
@@ -1,21 +1,37 @@
1
1
  module PLSQL
2
2
 
3
3
  module ProcedureClassMethods
4
- def find(schema, procedure, package = nil)
5
- if package.nil? && schema.select_first("
4
+ def find(schema, procedure, package = nil, override_schema_name = nil)
5
+ if package.nil?
6
+ if schema.select_first("
6
7
  SELECT object_name FROM all_objects
7
8
  WHERE owner = :owner
8
9
  AND object_name = :object_name
9
- AND object_type IN ('PROCEDURE','FUNCTION')
10
- ", schema.schema_name, procedure.to_s.upcase)
11
- new(schema, procedure)
10
+ AND object_type IN ('PROCEDURE','FUNCTION')",
11
+ schema.schema_name, procedure.to_s.upcase)
12
+ new(schema, procedure)
13
+ # search for synonym
14
+ elsif (row = schema.select_first("
15
+ SELECT o.owner, o.object_name
16
+ FROM all_synonyms s, all_objects o
17
+ WHERE s.owner IN (:owner, 'PUBLIC')
18
+ AND s.synonym_name = :synonym_name
19
+ AND o.owner = s.table_owner
20
+ AND o.object_name = s.table_name
21
+ AND o.object_type IN ('PROCEDURE','FUNCTION')
22
+ ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)",
23
+ schema.schema_name, procedure.to_s.upcase))
24
+ new(schema, row[1], nil, row[0])
25
+ else
26
+ nil
27
+ end
12
28
  elsif package && schema.select_first("
13
29
  SELECT object_name FROM all_procedures
14
30
  WHERE owner = :owner
15
31
  AND object_name = :object_name
16
32
  AND procedure_name = :procedure_name
17
- ", schema.schema_name, package, procedure.to_s.upcase)
18
- new(schema, procedure, package)
33
+ ", override_schema_name || schema.schema_name, package, procedure.to_s.upcase)
34
+ new(schema, procedure, package, override_schema_name)
19
35
  else
20
36
  nil
21
37
  end
@@ -25,8 +41,9 @@ module PLSQL
25
41
  class Procedure
26
42
  extend ProcedureClassMethods
27
43
 
28
- def initialize(schema, procedure, package = nil)
44
+ def initialize(schema, procedure, package = nil, override_schema_name = nil)
29
45
  @schema = schema
46
+ @schema_name = override_schema_name || schema.schema_name
30
47
  @procedure = procedure.to_s.upcase
31
48
  @package = package
32
49
  @arguments = {}
@@ -42,7 +59,7 @@ module PLSQL
42
59
  WHERE o.owner = :owner
43
60
  AND o.object_name = :object_name
44
61
  AND o.object_type <> 'PACKAGE BODY'
45
- ", @schema.schema_name, @package ? @package : @procedure
62
+ ", @schema_name, @package ? @package : @procedure
46
63
  )[0] rescue nil
47
64
  num_rows = @schema.connection.select_all("
48
65
  SELECT a.argument_name, a.position, a.data_type, a.in_out, a.data_length, a.data_precision, a.data_scale, a.overload
@@ -51,7 +68,7 @@ module PLSQL
51
68
  AND a.owner = :owner
52
69
  AND a.object_name = :procedure_name
53
70
  AND NVL(a.package_name,'nil') = :package
54
- ", object_id, @schema.schema_name, @procedure, @package ? @package : 'nil'
71
+ ", object_id, @schema_name, @procedure, @package ? @package : 'nil'
55
72
  ) do |r|
56
73
 
57
74
  argument_name, position, data_type, in_out, data_length, data_precision, data_scale, overload = r
@@ -122,7 +139,7 @@ module PLSQL
122
139
 
123
140
  sql = "BEGIN\n"
124
141
  sql << ":return := " if @return[overload]
125
- sql << "#{@schema.schema_name}." if @schema
142
+ sql << "#{@schema_name}." if @schema_name
126
143
  sql << "#{@package}." if @package
127
144
  sql << "#{@procedure}("
128
145