saberma-activerecord-oracle_enhanced-adapter-nvarchar2 1.2.1

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 (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