activerecord-oracle_enhanced-adapter 1.1.9 → 1.2.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.
Files changed (22) hide show
  1. data/History.txt +11 -0
  2. data/README.txt +6 -2
  3. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  4. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +771 -907
  5. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +71 -0
  6. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +2 -2
  8. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +352 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +346 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +2 -1
  11. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +2 -2
  13. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +200 -97
  14. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +170 -0
  15. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
  16. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +11 -6
  17. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +148 -53
  18. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +13 -5
  19. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +27 -0
  20. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +10 -6
  21. data/spec/spec_helper.rb +51 -6
  22. metadata +11 -2
@@ -0,0 +1,346 @@
1
+ require 'delegate'
2
+
3
+ begin
4
+ require 'oci8' unless self.class.const_defined? :OCI8
5
+
6
+ # RSI: added mapping for TIMESTAMP / WITH TIME ZONE / LOCAL TIME ZONE types
7
+ # currently Ruby-OCI8 does not support fractional seconds for timestamps
8
+ OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP] = OCI8::BindType::OraDate
9
+ OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_TZ] = OCI8::BindType::OraDate
10
+ OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_LTZ] = OCI8::BindType::OraDate
11
+ rescue LoadError
12
+ # OCI8 driver is unavailable.
13
+ error_message = "ERROR: ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. "+
14
+ "Please install ruby-oci8 library or gem."
15
+ if defined?(RAILS_DEFAULT_LOGGER)
16
+ RAILS_DEFAULT_LOGGER.error error_message
17
+ else
18
+ STDERR.puts error_message
19
+ end
20
+ raise LoadError
21
+ end
22
+
23
+ module ActiveRecord
24
+ module ConnectionAdapters
25
+
26
+ # OCI database interface for MRI
27
+ class OracleEnhancedOCIConnection < OracleEnhancedConnection
28
+
29
+ def initialize(config)
30
+ @raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory)
31
+ end
32
+
33
+ def auto_retry
34
+ @raw_connection.auto_retry if @raw_connection
35
+ end
36
+
37
+ def auto_retry=(value)
38
+ @raw_connection.auto_retry = value if @raw_connection
39
+ end
40
+
41
+ def logoff
42
+ @raw_connection.logoff
43
+ @raw_connection.active = false
44
+ end
45
+
46
+ def commit
47
+ @raw_connection.commit
48
+ end
49
+
50
+ def rollback
51
+ @raw_connection.rollback
52
+ end
53
+
54
+ def autocommit?
55
+ @raw_connection.autocommit?
56
+ end
57
+
58
+ def autocommit=(value)
59
+ @raw_connection.autocommit = value
60
+ end
61
+
62
+ # Checks connection, returns true if active. Note that ping actively
63
+ # checks the connection, while #active? simply returns the last
64
+ # known state.
65
+ def ping
66
+ @raw_connection.ping
67
+ rescue OCIException => e
68
+ raise OracleEnhancedConnectionException, e.message
69
+ end
70
+
71
+ def active?
72
+ @raw_connection.active?
73
+ end
74
+
75
+ def reset!
76
+ @raw_connection.reset!
77
+ rescue OCIException => e
78
+ raise OracleEnhancedConnectionException, e.message
79
+ end
80
+
81
+ def exec(sql, *bindvars, &block)
82
+ @raw_connection.exec(sql, *bindvars, &block)
83
+ end
84
+
85
+ def select(sql, name = nil, return_column_names = false)
86
+ cursor = @raw_connection.exec(sql)
87
+ cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
88
+ rows = []
89
+
90
+ while row = cursor.fetch
91
+ hash = Hash.new
92
+
93
+ cols.each_with_index do |col, i|
94
+ hash[col] =
95
+ case row[i]
96
+ when OCI8::LOB
97
+ if name == 'Writable Large Object'
98
+ row[i]
99
+ else
100
+ data = row[i].read
101
+ # In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
102
+ data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && row[i].is_a?(OCI8::BLOB)
103
+ data
104
+ end
105
+ # ruby-oci8 1.0 returns OraDate
106
+ when OraDate
107
+ d = row[i]
108
+ # RSI: added emulate_dates_by_column_name functionality
109
+ # if emulate_dates_by_column_name && self.class.is_date_column?(col)
110
+ # d.to_date
111
+ # elsif
112
+ if OracleEnhancedAdapter.emulate_dates && (d.hour == 0 && d.minute == 0 && d.second == 0)
113
+ d.to_date
114
+ else
115
+ # code from Time.time_with_datetime_fallback
116
+ begin
117
+ Time.send(Base.default_timezone, d.year, d.month, d.day, d.hour, d.minute, d.second)
118
+ rescue
119
+ offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
120
+ ::DateTime.civil(d.year, d.month, d.day, d.hour, d.minute, d.second, offset)
121
+ end
122
+ end
123
+ # ruby-oci8 2.0 returns Time or DateTime
124
+ when Time, DateTime
125
+ d = row[i]
126
+ if OracleEnhancedAdapter.emulate_dates && (d.hour == 0 && d.min == 0 && d.sec == 0)
127
+ d.to_date
128
+ else
129
+ # recreate Time or DateTime using Base.default_timezone
130
+ begin
131
+ Time.send(Base.default_timezone, d.year, d.month, d.day, d.hour, d.min, d.sec)
132
+ rescue
133
+ offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
134
+ ::DateTime.civil(d.year, d.month, d.day, d.hour, d.min, d.sec, offset)
135
+ end
136
+ end
137
+ # RSI: added emulate_integers_by_column_name functionality
138
+ when Float
139
+ n = row[i]
140
+ if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
141
+ n.to_i
142
+ else
143
+ n
144
+ end
145
+ # ruby-oci8 2.0 returns OraNumber - convert it to Integer or BigDecimal
146
+ when OraNumber
147
+ n = row[i]
148
+ n == (n_to_i = n.to_i) ? n_to_i : BigDecimal.new(n.to_s)
149
+ else row[i]
150
+ end unless col == 'raw_rnum_'
151
+ end
152
+
153
+ rows << hash
154
+ end
155
+
156
+ return_column_names ? [rows, cols] : rows
157
+ ensure
158
+ cursor.close if cursor
159
+ end
160
+
161
+ def write_lob(lob, value, is_binary = false)
162
+ lob.write value
163
+ end
164
+
165
+ def describe(name)
166
+ quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
167
+ @raw_connection.describe(quoted_name)
168
+ rescue OCIException => e
169
+ raise OracleEnhancedConnectionException, e.message
170
+ end
171
+
172
+ end
173
+
174
+ # The OracleEnhancedOCIFactory factors out the code necessary to connect and
175
+ # configure an Oracle/OCI connection.
176
+ class OracleEnhancedOCIFactory #:nodoc:
177
+ def self.new_connection(config)
178
+ username, password, database = config[:username].to_s, config[:password].to_s, config[:database].to_s
179
+ privilege = config[:privilege] && config[:privilege].to_sym
180
+ async = config[:allow_concurrency]
181
+ prefetch_rows = config[:prefetch_rows] || 100
182
+ cursor_sharing = config[:cursor_sharing] || 'similar'
183
+
184
+ conn = OCI8.new username, password, database, privilege
185
+ conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
186
+ conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
187
+ conn.autocommit = true
188
+ conn.non_blocking = true if async
189
+ conn.prefetch_rows = prefetch_rows
190
+ conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
191
+ conn
192
+ end
193
+ end
194
+
195
+
196
+ end
197
+ end
198
+
199
+
200
+
201
+ class OCI8 #:nodoc:
202
+
203
+ class Cursor #:nodoc:
204
+ if method_defined? :define_a_column
205
+ # This OCI8 patch is required with the ruby-oci8 1.0.x or lower.
206
+ # Set OCI8::BindType::Mapping[] to change the column type
207
+ # when using ruby-oci8 2.0.
208
+
209
+ alias :enhanced_define_a_column_pre_ar :define_a_column
210
+ def define_a_column(i)
211
+ case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
212
+ when 8; @stmt.defineByPos(i, String, 65535) # Read LONG values
213
+ when 187; @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
214
+ when 108
215
+ if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
216
+ @stmt.defineByPos(i, String, 65535)
217
+ else
218
+ raise 'unsupported datatype'
219
+ end
220
+ else enhanced_define_a_column_pre_ar i
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ if OCI8.public_method_defined?(:describe_table)
227
+ # ruby-oci8 2.0 or upper
228
+
229
+ def describe(name)
230
+ info = describe_table(name.to_s)
231
+ raise %Q{"DESC #{name}" failed} if info.nil?
232
+ [info.obj_schema, info.obj_name]
233
+ end
234
+ else
235
+ # ruby-oci8 1.0.x or lower
236
+
237
+ # missing constant from oci8 < 0.1.14
238
+ OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
239
+
240
+ # Uses the describeAny OCI call to find the target owner and table_name
241
+ # indicated by +name+, parsing through synonynms as necessary. Returns
242
+ # an array of [owner, table_name].
243
+ def describe(name)
244
+ @desc ||= @@env.alloc(OCIDescribe)
245
+ @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
246
+ do_ocicall(@ctx) { @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) } rescue raise %Q{"DESC #{name}" failed; does it exist?}
247
+ info = @desc.attrGet(OCI_ATTR_PARAM)
248
+
249
+ case info.attrGet(OCI_ATTR_PTYPE)
250
+ when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
251
+ owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
252
+ table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
253
+ [owner, table_name]
254
+ when OCI_PTYPE_SYN
255
+ schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
256
+ name = info.attrGet(OCI_ATTR_NAME)
257
+ describe(schema + '.' + name)
258
+ else raise %Q{"DESC #{name}" failed; not a table or view.}
259
+ end
260
+ end
261
+ end
262
+
263
+ end
264
+
265
+ # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
266
+ # reset functionality. If a call to #exec fails, and autocommit is turned on
267
+ # (ie., we're not in the middle of a longer transaction), it will
268
+ # automatically reconnect and try again. If autocommit is turned off,
269
+ # this would be dangerous (as the earlier part of the implied transaction
270
+ # may have failed silently if the connection died) -- so instead the
271
+ # connection is marked as dead, to be reconnected on it's next use.
272
+ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
273
+ attr_accessor :active
274
+ alias :active? :active
275
+
276
+ cattr_accessor :auto_retry
277
+ class << self
278
+ alias :auto_retry? :auto_retry
279
+ end
280
+ @@auto_retry = false
281
+
282
+ def initialize(config, factory)
283
+ @active = true
284
+ @config = config
285
+ @factory = factory
286
+ @connection = @factory.new_connection @config
287
+ super @connection
288
+ end
289
+
290
+ # Checks connection, returns true if active. Note that ping actively
291
+ # checks the connection, while #active? simply returns the last
292
+ # known state.
293
+ def ping
294
+ @connection.exec("select 1 from dual") { |r| nil }
295
+ @active = true
296
+ rescue
297
+ @active = false
298
+ raise
299
+ end
300
+
301
+ # Resets connection, by logging off and creating a new connection.
302
+ def reset!
303
+ logoff rescue nil
304
+ begin
305
+ @connection = @factory.new_connection @config
306
+ __setobj__ @connection
307
+ @active = true
308
+ rescue
309
+ @active = false
310
+ raise
311
+ end
312
+ end
313
+
314
+ # ORA-00028: your session has been killed
315
+ # ORA-01012: not logged on
316
+ # ORA-03113: end-of-file on communication channel
317
+ # ORA-03114: not connected to ORACLE
318
+ # ORA-03135: connection lost contact
319
+ LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ]
320
+
321
+ # Adds auto-recovery functionality.
322
+ #
323
+ # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
324
+ def exec(sql, *bindvars, &block)
325
+ should_retry = self.class.auto_retry? && autocommit?
326
+
327
+ begin
328
+ @connection.exec(sql, *bindvars, &block)
329
+ rescue OCIException => e
330
+ raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
331
+ @active = false
332
+ raise unless should_retry
333
+ should_retry = false
334
+ reset! rescue nil
335
+ retry
336
+ end
337
+ end
338
+
339
+ # RSI: otherwise not working in Ruby 1.9.1
340
+ if RUBY_VERSION =~ /^1\.9/
341
+ def describe(name)
342
+ @connection.describe(name)
343
+ end
344
+ end
345
+
346
+ end
@@ -40,7 +40,8 @@ module ActiveRecord #:nodoc:
40
40
  base.instance_eval do
41
41
  alias_method_chain :create, :custom_method
42
42
  # insert after dirty checking in Rails 2.1
43
- if private_instance_methods.include?('update_without_dirty')
43
+ # in Ruby 1.9 methods names are returned as symbols
44
+ if private_instance_methods.include?('update_without_dirty') || private_instance_methods.include?(:update_without_dirty)
44
45
  alias_method :update_without_custom_method, :update_without_dirty
45
46
  alias_method :update_without_dirty, :update_with_custom_method
46
47
  else
@@ -0,0 +1,126 @@
1
+ module ActiveRecord #:nodoc:
2
+ module ConnectionAdapters #:nodoc:
3
+ module OracleEnhancedReservedWords #:nodoc:
4
+
5
+ RESERVED_WORDS = {
6
+ "ACCESS" => true,
7
+ "ADD" => true,
8
+ "ALL" => true,
9
+ "ALTER" => true,
10
+ "AND" => true,
11
+ "ANY" => true,
12
+ "AS" => true,
13
+ "ASC" => true,
14
+ "AUDIT" => true,
15
+ "BETWEEN" => true,
16
+ "BY" => true,
17
+ "CHAR" => true,
18
+ "CHECK" => true,
19
+ "CLUSTER" => true,
20
+ "COLUMN" => true,
21
+ "COMMENT" => true,
22
+ "COMPRESS" => true,
23
+ "CONNECT" => true,
24
+ "CREATE" => true,
25
+ "CURRENT" => true,
26
+ "DATE" => true,
27
+ "DECIMAL" => true,
28
+ "DEFAULT" => true,
29
+ "DELETE" => true,
30
+ "DESC" => true,
31
+ "DISTINCT" => true,
32
+ "DROP" => true,
33
+ "ELSE" => true,
34
+ "EXCLUSIVE" => true,
35
+ "EXISTS" => true,
36
+ "FILE" => true,
37
+ "FLOAT" => true,
38
+ "FOR" => true,
39
+ "FROM" => true,
40
+ "GRANT" => true,
41
+ "GROUP" => true,
42
+ "HAVING" => true,
43
+ "IDENTIFIED" => true,
44
+ "IMMEDIATE" => true,
45
+ "IN" => true,
46
+ "INCREMENT" => true,
47
+ "INDEX" => true,
48
+ "INITIAL" => true,
49
+ "INSERT" => true,
50
+ "INTEGER" => true,
51
+ "INTERSECT" => true,
52
+ "INTO" => true,
53
+ "IS" => true,
54
+ "LEVEL" => true,
55
+ "LIKE" => true,
56
+ "LOCK" => true,
57
+ "LONG" => true,
58
+ "MAXEXTENTS" => true,
59
+ "MINUS" => true,
60
+ "MLSLABEL" => true,
61
+ "MODE" => true,
62
+ "MODIFY" => true,
63
+ "NOAUDIT" => true,
64
+ "NOCOMPRESS" => true,
65
+ "NOT" => true,
66
+ "NOWAIT" => true,
67
+ "NULL" => true,
68
+ "NUMBER" => true,
69
+ "OF" => true,
70
+ "OFFLINE" => true,
71
+ "ON" => true,
72
+ "ONLINE" => true,
73
+ "OPTION" => true,
74
+ "OR" => true,
75
+ "ORDER" => true,
76
+ "PCTFREE" => true,
77
+ "PRIOR" => true,
78
+ "PRIVILEGES" => true,
79
+ "PUBLIC" => true,
80
+ "RAW" => true,
81
+ "RENAME" => true,
82
+ "RESOURCE" => true,
83
+ "REVOKE" => true,
84
+ "ROW" => true,
85
+ "ROWID" => true,
86
+ "ROWNUM" => true,
87
+ "ROWS" => true,
88
+ "SELECT" => true,
89
+ "SESSION" => true,
90
+ "SET" => true,
91
+ "SHARE" => true,
92
+ "SIZE" => true,
93
+ "SMALLINT" => true,
94
+ "START" => true,
95
+ "SUCCESSFUL" => true,
96
+ "SYNONYM" => true,
97
+ "SYSDATE" => true,
98
+ "TABLE" => true,
99
+ "THEN" => true,
100
+ "TO" => true,
101
+ "TRIGGER" => true,
102
+ "UID" => true,
103
+ "UNION" => true,
104
+ "UNIQUE" => true,
105
+ "UPDATE" => true,
106
+ "USER" => true,
107
+ "VALIDATE" => true,
108
+ "VALUES" => true,
109
+ "VARCHAR" => true,
110
+ "VARCHAR2" => true,
111
+ "VIEW" => true,
112
+ "WHENEVER" => true,
113
+ "WHERE" => true,
114
+ "WITH" => true
115
+ }
116
+
117
+ def quote_oracle_reserved_words(name)
118
+ RESERVED_WORDS[name.to_s.upcase].nil? ? name : "\"#{name}\""
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
125
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedReservedWords
126
+ end