saberma-activerecord-oracle_enhanced-adapter-nvarchar2 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/History.txt +111 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +26 -0
  4. data/README.rdoc +66 -0
  5. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  6. data/lib/active_record/connection_adapters/oracle_enhanced.rake +44 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1017 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +71 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +341 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +351 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +124 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +7 -0
  18. data/oracle-enhanced.gemspec +59 -0
  19. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +590 -0
  20. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +170 -0
  21. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
  22. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +103 -0
  23. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +943 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +27 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +340 -0
  27. data/spec/spec.opts +6 -0
  28. data/spec/spec_helper.rb +94 -0
  29. metadata +94 -0
@@ -0,0 +1,351 @@
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 = []
88
+ # Ignore raw_rnum_ which is used to simulate LIMIT and OFFSET
89
+ cursor.get_col_names.each do |col_name|
90
+ col_name = oracle_downcase(col_name)
91
+ cols << col_name unless col_name == 'raw_rnum_'
92
+ end
93
+ # Reuse the same hash for all rows
94
+ column_hash = {}
95
+ cols.each {|c| column_hash[c] = nil}
96
+ rows = []
97
+ get_lob_value = !(name == 'Writable Large Object')
98
+
99
+ while row = cursor.fetch
100
+ hash = column_hash.dup
101
+
102
+ cols.each_with_index do |col, i|
103
+ hash[col] =
104
+ case v = row[i]
105
+ # RSI: added emulate_integers_by_column_name functionality
106
+ when Float
107
+ # if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
108
+ # v.to_i
109
+ # else
110
+ # v
111
+ # end
112
+ v == (v_to_i = v.to_i) ? v_to_i : v
113
+ # ruby-oci8 2.0 returns OraNumber - convert it to Integer or BigDecimal
114
+ when OraNumber
115
+ v == (v_to_i = v.to_i) ? v_to_i : BigDecimal.new(v.to_s)
116
+ when String
117
+ v
118
+ when OCI8::LOB
119
+ if get_lob_value
120
+ data = v.read
121
+ # In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
122
+ data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && v.is_a?(OCI8::BLOB)
123
+ data
124
+ else
125
+ v
126
+ end
127
+ # ruby-oci8 1.0 returns OraDate
128
+ when OraDate
129
+ # RSI: added emulate_dates_by_column_name functionality
130
+ if OracleEnhancedAdapter.emulate_dates && (v.hour == 0 && v.minute == 0 && v.second == 0)
131
+ v.to_date
132
+ else
133
+ # code from Time.time_with_datetime_fallback
134
+ begin
135
+ Time.send(Base.default_timezone, v.year, v.month, v.day, v.hour, v.minute, v.second)
136
+ rescue
137
+ offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
138
+ ::DateTime.civil(v.year, v.month, v.day, v.hour, v.minute, v.second, offset)
139
+ end
140
+ end
141
+ # ruby-oci8 2.0 returns Time or DateTime
142
+ when Time, DateTime
143
+ if OracleEnhancedAdapter.emulate_dates && (v.hour == 0 && v.min == 0 && v.sec == 0)
144
+ v.to_date
145
+ else
146
+ # recreate Time or DateTime using Base.default_timezone
147
+ begin
148
+ Time.send(Base.default_timezone, v.year, v.month, v.day, v.hour, v.min, v.sec)
149
+ rescue
150
+ offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
151
+ ::DateTime.civil(v.year, v.month, v.day, v.hour, v.min, v.sec, offset)
152
+ end
153
+ end
154
+ else v
155
+ end
156
+ end
157
+
158
+ rows << hash
159
+ end
160
+
161
+ return_column_names ? [rows, cols] : rows
162
+ ensure
163
+ cursor.close if cursor
164
+ end
165
+
166
+ def write_lob(lob, value, is_binary = false)
167
+ lob.write value
168
+ end
169
+
170
+ def describe(name)
171
+ quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
172
+ @raw_connection.describe(quoted_name)
173
+ rescue OCIException => e
174
+ raise OracleEnhancedConnectionException, e.message
175
+ end
176
+
177
+ end
178
+
179
+ # The OracleEnhancedOCIFactory factors out the code necessary to connect and
180
+ # configure an Oracle/OCI connection.
181
+ class OracleEnhancedOCIFactory #:nodoc:
182
+ def self.new_connection(config)
183
+ username, password, database = config[:username].to_s, config[:password].to_s, config[:database].to_s
184
+ privilege = config[:privilege] && config[:privilege].to_sym
185
+ async = config[:allow_concurrency]
186
+ prefetch_rows = config[:prefetch_rows] || 100
187
+ cursor_sharing = config[:cursor_sharing] || 'similar'
188
+
189
+ conn = OCI8.new username, password, database, privilege
190
+ conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
191
+ conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
192
+ conn.autocommit = true
193
+ conn.non_blocking = true if async
194
+ conn.prefetch_rows = prefetch_rows
195
+ conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
196
+ conn
197
+ end
198
+ end
199
+
200
+
201
+ end
202
+ end
203
+
204
+
205
+
206
+ class OCI8 #:nodoc:
207
+
208
+ class Cursor #:nodoc:
209
+ if method_defined? :define_a_column
210
+ # This OCI8 patch is required with the ruby-oci8 1.0.x or lower.
211
+ # Set OCI8::BindType::Mapping[] to change the column type
212
+ # when using ruby-oci8 2.0.
213
+
214
+ alias :enhanced_define_a_column_pre_ar :define_a_column
215
+ def define_a_column(i)
216
+ case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
217
+ when 8; @stmt.defineByPos(i, String, 65535) # Read LONG values
218
+ when 187; @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
219
+ when 108
220
+ if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
221
+ @stmt.defineByPos(i, String, 65535)
222
+ else
223
+ raise 'unsupported datatype'
224
+ end
225
+ else enhanced_define_a_column_pre_ar i
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ if OCI8.public_method_defined?(:describe_table)
232
+ # ruby-oci8 2.0 or upper
233
+
234
+ def describe(name)
235
+ info = describe_table(name.to_s)
236
+ raise %Q{"DESC #{name}" failed} if info.nil?
237
+ [info.obj_schema, info.obj_name]
238
+ end
239
+ else
240
+ # ruby-oci8 1.0.x or lower
241
+
242
+ # missing constant from oci8 < 0.1.14
243
+ OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
244
+
245
+ # Uses the describeAny OCI call to find the target owner and table_name
246
+ # indicated by +name+, parsing through synonynms as necessary. Returns
247
+ # an array of [owner, table_name].
248
+ def describe(name)
249
+ @desc ||= @@env.alloc(OCIDescribe)
250
+ @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
251
+ do_ocicall(@ctx) { @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) } rescue raise %Q{"DESC #{name}" failed; does it exist?}
252
+ info = @desc.attrGet(OCI_ATTR_PARAM)
253
+
254
+ case info.attrGet(OCI_ATTR_PTYPE)
255
+ when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
256
+ owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
257
+ table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
258
+ [owner, table_name]
259
+ when OCI_PTYPE_SYN
260
+ schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
261
+ name = info.attrGet(OCI_ATTR_NAME)
262
+ describe(schema + '.' + name)
263
+ else raise %Q{"DESC #{name}" failed; not a table or view.}
264
+ end
265
+ end
266
+ end
267
+
268
+ end
269
+
270
+ # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
271
+ # reset functionality. If a call to #exec fails, and autocommit is turned on
272
+ # (ie., we're not in the middle of a longer transaction), it will
273
+ # automatically reconnect and try again. If autocommit is turned off,
274
+ # this would be dangerous (as the earlier part of the implied transaction
275
+ # may have failed silently if the connection died) -- so instead the
276
+ # connection is marked as dead, to be reconnected on it's next use.
277
+ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
278
+ attr_accessor :active
279
+ alias :active? :active
280
+
281
+ cattr_accessor :auto_retry
282
+ class << self
283
+ alias :auto_retry? :auto_retry
284
+ end
285
+ @@auto_retry = false
286
+
287
+ def initialize(config, factory)
288
+ @active = true
289
+ @config = config
290
+ @factory = factory
291
+ @connection = @factory.new_connection @config
292
+ super @connection
293
+ end
294
+
295
+ # Checks connection, returns true if active. Note that ping actively
296
+ # checks the connection, while #active? simply returns the last
297
+ # known state.
298
+ def ping
299
+ @connection.exec("select 1 from dual") { |r| nil }
300
+ @active = true
301
+ rescue
302
+ @active = false
303
+ raise
304
+ end
305
+
306
+ # Resets connection, by logging off and creating a new connection.
307
+ def reset!
308
+ logoff rescue nil
309
+ begin
310
+ @connection = @factory.new_connection @config
311
+ __setobj__ @connection
312
+ @active = true
313
+ rescue
314
+ @active = false
315
+ raise
316
+ end
317
+ end
318
+
319
+ # ORA-00028: your session has been killed
320
+ # ORA-01012: not logged on
321
+ # ORA-03113: end-of-file on communication channel
322
+ # ORA-03114: not connected to ORACLE
323
+ # ORA-03135: connection lost contact
324
+ LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ]
325
+
326
+ # Adds auto-recovery functionality.
327
+ #
328
+ # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
329
+ def exec(sql, *bindvars, &block)
330
+ should_retry = self.class.auto_retry? && autocommit?
331
+
332
+ begin
333
+ @connection.exec(sql, *bindvars, &block)
334
+ rescue OCIException => e
335
+ raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
336
+ @active = false
337
+ raise unless should_retry
338
+ should_retry = false
339
+ reset! rescue nil
340
+ retry
341
+ end
342
+ end
343
+
344
+ # RSI: otherwise not working in Ruby 1.9.1
345
+ if RUBY_VERSION =~ /^1\.9/
346
+ def describe(name)
347
+ @connection.describe(name)
348
+ end
349
+ end
350
+
351
+ end
@@ -0,0 +1,124 @@
1
+ # define accessors before requiring ruby-plsql as these accessors are used in clob writing callback and should be
2
+ # available also if ruby-plsql could not be loaded
3
+ ActiveRecord::Base.class_eval do
4
+ class_inheritable_accessor :custom_create_method, :custom_update_method, :custom_delete_method
5
+ end
6
+
7
+ require 'ruby_plsql'
8
+ require 'activesupport'
9
+
10
+ module ActiveRecord #:nodoc:
11
+ module ConnectionAdapters #:nodoc:
12
+ module OracleEnhancedProcedures #:nodoc:
13
+
14
+ module ClassMethods
15
+ def set_create_method(&block)
16
+ include_with_custom_methods
17
+ self.custom_create_method = block
18
+ end
19
+
20
+ def set_update_method(&block)
21
+ include_with_custom_methods
22
+ self.custom_update_method = block
23
+ end
24
+
25
+ def set_delete_method(&block)
26
+ include_with_custom_methods
27
+ self.custom_delete_method = block
28
+ end
29
+
30
+ private
31
+ def include_with_custom_methods
32
+ unless included_modules.include? InstanceMethods
33
+ include InstanceMethods
34
+ end
35
+ end
36
+ end
37
+
38
+ module InstanceMethods
39
+ def self.included(base)
40
+ base.instance_eval do
41
+ if private_instance_methods.include?('create_without_callbacks') || private_instance_methods.include?(:create_without_callbacks)
42
+ alias_method :create_without_custom_method, :create_without_callbacks
43
+ alias_method :create_without_callbacks, :create_with_custom_method
44
+ else
45
+ alias_method_chain :create, :custom_method
46
+ end
47
+ # insert after dirty checking in Rails 2.1
48
+ # in Ruby 1.9 methods names are returned as symbols
49
+ if private_instance_methods.include?('update_without_dirty') || private_instance_methods.include?(:update_without_dirty)
50
+ alias_method :update_without_custom_method, :update_without_dirty
51
+ alias_method :update_without_dirty, :update_with_custom_method
52
+ elsif private_instance_methods.include?('update_without_callbacks') || private_instance_methods.include?(:update_without_callbacks)
53
+ alias_method :update_without_custom_method, :update_without_callbacks
54
+ alias_method :update_without_callbacks, :update_with_custom_method
55
+ else
56
+ alias_method_chain :update, :custom_method
57
+ end
58
+ private :create, :update
59
+ if public_instance_methods.include?('destroy_without_callbacks') || public_instance_methods.include?(:destroy_without_callbacks)
60
+ alias_method :destroy_without_custom_method, :destroy_without_callbacks
61
+ alias_method :destroy_without_callbacks, :destroy_with_custom_method
62
+ else
63
+ alias_method_chain :destroy, :custom_method
64
+ end
65
+ public :destroy
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # Creates a record with custom create method
72
+ # and returns its id.
73
+ def create_with_custom_method
74
+ # check if class has custom create method
75
+ return create_without_custom_method unless self.class.custom_create_method
76
+ self.class.connection.log_custom_method("custom create method", "#{self.class.name} Create") do
77
+ self.id = self.class.custom_create_method.bind(self).call
78
+ end
79
+ @new_record = false
80
+ id
81
+ end
82
+
83
+ # Updates the associated record with custom update method
84
+ # Returns the number of affected rows.
85
+ def update_with_custom_method(attribute_names = @attributes.keys)
86
+ # check if class has custom create method
87
+ return update_without_custom_method unless self.class.custom_update_method
88
+ return 0 if attribute_names.empty?
89
+ self.class.connection.log_custom_method("custom update method with #{self.class.primary_key}=#{self.id}", "#{self.class.name} Update") do
90
+ self.class.custom_update_method.bind(self).call
91
+ end
92
+ 1
93
+ end
94
+
95
+ # Deletes the record in the database with custom delete method
96
+ # and freezes this instance to reflect that no changes should
97
+ # be made (since they can't be persisted).
98
+ def destroy_with_custom_method
99
+ # check if class has custom create method
100
+ return destroy_without_custom_method unless self.class.custom_delete_method
101
+ unless new_record?
102
+ self.class.connection.log_custom_method("custom delete method with #{self.class.primary_key}=#{self.id}", "#{self.class.name} Destroy") do
103
+ self.class.custom_delete_method.bind(self).call
104
+ end
105
+ end
106
+
107
+ freeze
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+ end
114
+ end
115
+
116
+ ActiveRecord::Base.class_eval do
117
+ extend ActiveRecord::ConnectionAdapters::OracleEnhancedProcedures::ClassMethods
118
+ end
119
+
120
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
121
+ # public alias to log method which could be used from other objects
122
+ alias_method :log_custom_method, :log
123
+ public :log_custom_method
124
+ end