activerecord-oracle_enhanced-adapter 1.1.9 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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