activerecord-oracle_enhanced-adapter 1.4.3 → 5.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 (100) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +1162 -2
  3. data/README.md +567 -155
  4. data/VERSION +1 -1
  5. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +3 -1
  6. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +19 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +132 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +345 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +52 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +280 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +64 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +59 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +538 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +46 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +435 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +196 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +164 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +79 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +194 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +709 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +28 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +353 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +33 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +385 -1083
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +20 -0
  30. data/lib/active_record/type/oracle_enhanced/integer.rb +15 -0
  31. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  32. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  34. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  35. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  36. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  37. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  38. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  39. data/lib/activerecord-oracle_enhanced-adapter.rb +5 -13
  40. data/spec/active_record/connection_adapters/{oracle_enhanced_emulate_oracle_adapter_spec.rb → emulation/oracle_adapter_spec.rb} +5 -4
  41. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +469 -0
  42. data/spec/active_record/connection_adapters/{oracle_enhanced_context_index_spec.rb → oracle_enhanced/context_index_spec.rb} +140 -128
  43. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +112 -0
  44. data/spec/active_record/connection_adapters/{oracle_enhanced_dbms_output_spec.rb → oracle_enhanced/dbms_output_spec.rb} +13 -13
  45. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +365 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +196 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1433 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +478 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +385 -550
  51. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +92 -1249
  52. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  53. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +208 -0
  54. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +139 -0
  55. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  56. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +91 -0
  57. data/spec/active_record/oracle_enhanced/type/json_spec.rb +57 -0
  58. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
  59. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  60. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +122 -0
  61. data/spec/active_record/oracle_enhanced/type/text_spec.rb +229 -0
  62. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +75 -0
  63. data/spec/spec_config.yaml.template +11 -0
  64. data/spec/spec_helper.rb +100 -93
  65. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  66. data/spec/support/alter_system_user_password.sql +2 -0
  67. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  68. metadata +105 -152
  69. data/.rspec +0 -2
  70. data/Gemfile +0 -52
  71. data/RUNNING_TESTS.md +0 -45
  72. data/Rakefile +0 -59
  73. data/activerecord-oracle_enhanced-adapter.gemspec +0 -130
  74. data/lib/active_record/connection_adapters/oracle_enhanced.rake +0 -105
  75. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +0 -41
  76. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +0 -121
  77. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +0 -151
  78. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +0 -119
  79. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -359
  80. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +0 -25
  81. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +0 -21
  82. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -46
  83. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +0 -572
  84. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +0 -497
  85. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +0 -260
  86. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -227
  87. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +0 -260
  88. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -428
  89. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
  90. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +0 -294
  91. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +0 -17
  92. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
  93. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +0 -334
  94. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +0 -19
  95. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +0 -113
  96. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +0 -141
  97. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +0 -378
  98. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +0 -440
  99. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +0 -1400
  100. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +0 -339
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module OracleEnhanced
6
+ module JDBCQuoting
7
+ def _type_cast(value)
8
+ case value
9
+ when ActiveModel::Type::Binary::Data
10
+ blob = Java::OracleSql::BLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::BLOB::DURATION_SESSION)
11
+ blob.setBytes(1, value.to_s.to_java_bytes)
12
+ blob
13
+ when Type::OracleEnhanced::Text::Data
14
+ clob = Java::OracleSql::CLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::CLOB::DURATION_SESSION)
15
+ clob.setString(1, value.to_s)
16
+ clob
17
+ when Type::OracleEnhanced::NationalCharacterText::Data
18
+ clob = Java::OracleSql::NCLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::NCLOB::DURATION_SESSION)
19
+ clob.setString(1, value.to_s)
20
+ clob
21
+ else
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ module ActiveRecord
31
+ module ConnectionAdapters
32
+ module OracleEnhanced
33
+ module Quoting
34
+ prepend JDBCQuoting
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord #:nodoc:
4
+ module ConnectionAdapters #:nodoc:
5
+ module OracleEnhanced #:nodoc:
6
+ module Lob #:nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :custom_create_method, :custom_update_method, :custom_delete_method
11
+
12
+ # After setting large objects to empty, select the OCI8::LOB
13
+ # and write back the data.
14
+ before_update :record_changed_lobs
15
+ after_update :enhanced_write_lobs
16
+ end
17
+
18
+ module ClassMethods
19
+ def lob_columns
20
+ columns.select do |column|
21
+ column.sql_type_metadata.sql_type =~ /LOB$/
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def enhanced_write_lobs
29
+ if self.class.connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
30
+ !(self.class.custom_create_method || self.class.custom_update_method)
31
+ self.class.connection.write_lobs(self.class.table_name, self.class, attributes, @changed_lob_columns)
32
+ end
33
+ end
34
+ def record_changed_lobs
35
+ @changed_lob_columns = self.class.lob_columns.select do |col|
36
+ self.will_save_change_to_attribute?(col.name) && !self.class.readonly_attributes.to_a.include?(col.name)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ ActiveSupport.on_load(:active_record) do
45
+ ActiveRecord::Base.send(:include, ActiveRecord::ConnectionAdapters::OracleEnhanced::Lob)
46
+ end
@@ -0,0 +1,435 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ begin
6
+ require "oci8"
7
+ rescue LoadError => e
8
+ # OCI8 driver is unavailable or failed to load a required library.
9
+ raise LoadError, "ERROR: '#{e.message}'. "\
10
+ "ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. "\
11
+ "You may need install ruby-oci8 gem."
12
+ end
13
+
14
+ # check ruby-oci8 version
15
+ required_oci8_version = [2, 2, 4]
16
+ oci8_version_ints = OCI8::VERSION.scan(/\d+/).map { |s| s.to_i }
17
+ if (oci8_version_ints <=> required_oci8_version) < 0
18
+ $stderr.puts <<-EOS.strip_heredoc
19
+ "ERROR: ruby-oci8 version #{OCI8::VERSION} is too old. Please install ruby-oci8 version #{required_oci8_version.join('.')} or later."
20
+ EOS
21
+
22
+ exit!
23
+ end
24
+
25
+ module ActiveRecord
26
+ module ConnectionAdapters
27
+ # OCI database interface for MRI
28
+ module OracleEnhanced
29
+ class OCIConnection < OracleEnhanced::Connection #:nodoc:
30
+ def initialize(config)
31
+ @raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory)
32
+ # default schema owner
33
+ @owner = config[:schema]
34
+ @owner ||= config[:username]
35
+ @owner = @owner.to_s.upcase
36
+ end
37
+
38
+ def raw_oci_connection
39
+ if @raw_connection.is_a? OCI8
40
+ @raw_connection
41
+ # ActiveRecord Oracle enhanced adapter puts OCI8EnhancedAutoRecover wrapper around OCI8
42
+ # in this case we need to pass original OCI8 connection
43
+ else
44
+ @raw_connection.instance_variable_get(:@connection)
45
+ end
46
+ end
47
+
48
+ def auto_retry
49
+ @raw_connection.auto_retry if @raw_connection
50
+ end
51
+
52
+ def auto_retry=(value)
53
+ @raw_connection.auto_retry = value if @raw_connection
54
+ end
55
+
56
+ def logoff
57
+ @raw_connection.logoff
58
+ @raw_connection.active = false
59
+ end
60
+
61
+ def commit
62
+ @raw_connection.commit
63
+ end
64
+
65
+ def rollback
66
+ @raw_connection.rollback
67
+ end
68
+
69
+ def autocommit?
70
+ @raw_connection.autocommit?
71
+ end
72
+
73
+ def autocommit=(value)
74
+ @raw_connection.autocommit = value
75
+ end
76
+
77
+ # Checks connection, returns true if active. Note that ping actively
78
+ # checks the connection, while #active? simply returns the last
79
+ # known state.
80
+ def ping
81
+ @raw_connection.ping
82
+ rescue OCIException => e
83
+ raise OracleEnhanced::ConnectionException, e.message
84
+ end
85
+
86
+ def active?
87
+ @raw_connection.active?
88
+ end
89
+
90
+ def reset!
91
+ @raw_connection.reset!
92
+ rescue OCIException => e
93
+ raise OracleEnhanced::ConnectionException, e.message
94
+ end
95
+
96
+ def exec(sql, *bindvars, &block)
97
+ @raw_connection.exec(sql, *bindvars, &block)
98
+ end
99
+
100
+ def prepare(sql)
101
+ Cursor.new(self, @raw_connection.parse(sql))
102
+ end
103
+
104
+ class Cursor
105
+ def initialize(connection, raw_cursor)
106
+ @connection = connection
107
+ @raw_cursor = raw_cursor
108
+ end
109
+
110
+ def bind_params(*bind_vars)
111
+ index = 1
112
+ bind_vars.flatten.each do |var|
113
+ if Hash === var
114
+ var.each { |key, val| bind_param key, val }
115
+ else
116
+ bind_param index, var
117
+ index += 1
118
+ end
119
+ end
120
+ end
121
+
122
+ def bind_param(position, value)
123
+ case value
124
+ when Type::OracleEnhanced::Raw
125
+ @raw_cursor.bind_param(position, OracleEnhanced::Quoting.encode_raw(value))
126
+ when ActiveModel::Type::Decimal
127
+ @raw_cursor.bind_param(position, BigDecimal.new(value.to_s))
128
+ when NilClass
129
+ @raw_cursor.bind_param(position, nil, String)
130
+ else
131
+ @raw_cursor.bind_param(position, value)
132
+ end
133
+ end
134
+
135
+ def bind_returning_param(position, bind_type)
136
+ @raw_cursor.bind_param(position, nil, bind_type)
137
+ end
138
+
139
+ def exec
140
+ @raw_cursor.exec
141
+ end
142
+
143
+ def exec_update
144
+ @raw_cursor.exec
145
+ end
146
+
147
+ def get_col_names
148
+ @raw_cursor.get_col_names
149
+ end
150
+
151
+ def fetch(options = {})
152
+ if row = @raw_cursor.fetch
153
+ get_lob_value = options[:get_lob_value]
154
+ row.map do |col|
155
+ @connection.typecast_result_value(col, get_lob_value)
156
+ end
157
+ end
158
+ end
159
+
160
+ def get_returning_param(position, type)
161
+ @raw_cursor[position]
162
+ end
163
+
164
+ def close
165
+ @raw_cursor.close
166
+ end
167
+ end
168
+
169
+ def select(sql, name = nil, return_column_names = false)
170
+ cursor = @raw_connection.exec(sql)
171
+ cols = []
172
+ # Ignore raw_rnum_ which is used to simulate LIMIT and OFFSET
173
+ cursor.get_col_names.each do |col_name|
174
+ col_name = _oracle_downcase(col_name)
175
+ cols << col_name unless col_name == "raw_rnum_"
176
+ end
177
+ # Reuse the same hash for all rows
178
+ column_hash = {}
179
+ cols.each { |c| column_hash[c] = nil }
180
+ rows = []
181
+ get_lob_value = !(name == "Writable Large Object")
182
+
183
+ while row = cursor.fetch
184
+ hash = column_hash.dup
185
+
186
+ cols.each_with_index do |col, i|
187
+ hash[col] = typecast_result_value(row[i], get_lob_value)
188
+ end
189
+
190
+ rows << hash
191
+ end
192
+
193
+ return_column_names ? [rows, cols] : rows
194
+ ensure
195
+ cursor.close if cursor
196
+ end
197
+
198
+ def write_lob(lob, value, is_binary = false)
199
+ lob.write value
200
+ end
201
+
202
+ def describe(name)
203
+ # fall back to SELECT based describe if using database link
204
+ return super if name.to_s.include?("@")
205
+ quoted_name = OracleEnhanced::Quoting.valid_table_name?(name) ? name : "\"#{name}\""
206
+ @raw_connection.describe(quoted_name)
207
+ rescue OCIException => e
208
+ if e.code == 4043
209
+ raise OracleEnhanced::ConnectionException, %Q{"DESC #{name}" failed; does it exist?}
210
+ else
211
+ # fall back to SELECT which can handle synonyms to database links
212
+ super
213
+ end
214
+ end
215
+
216
+ # Return OCIError error code
217
+ def error_code(exception)
218
+ case exception
219
+ when OCIError
220
+ exception.code
221
+ else
222
+ nil
223
+ end
224
+ end
225
+
226
+ def typecast_result_value(value, get_lob_value)
227
+ case value
228
+ when Integer
229
+ value
230
+ when String
231
+ value
232
+ when Float, BigDecimal
233
+ # return Integer if value is integer (to avoid issues with _before_type_cast values for id attributes)
234
+ value == (v_to_i = value.to_i) ? v_to_i : value
235
+ when OCI8::LOB
236
+ if get_lob_value
237
+ data = value.read || "" # if value.read returns nil, then we have an empty_clob() i.e. an empty string
238
+ # In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
239
+ data.force_encoding("ASCII-8BIT") if data.respond_to?(:force_encoding) && value.is_a?(OCI8::BLOB)
240
+ data
241
+ else
242
+ value
243
+ end
244
+ when Time, DateTime
245
+ create_time_with_default_timezone(value)
246
+ else
247
+ value
248
+ end
249
+ end
250
+
251
+ def database_version
252
+ @database_version ||= (version = raw_connection.oracle_server_version) && [version.major, version.minor]
253
+ end
254
+
255
+ private
256
+
257
+ def date_without_time?(value)
258
+ case value
259
+ when OraDate
260
+ value.hour == 0 && value.minute == 0 && value.second == 0
261
+ else
262
+ value.hour == 0 && value.min == 0 && value.sec == 0
263
+ end
264
+ end
265
+
266
+ def create_time_with_default_timezone(value)
267
+ year, month, day, hour, min, sec, usec = case value
268
+ when Time
269
+ [value.year, value.month, value.day, value.hour, value.min, value.sec, value.usec]
270
+ when OraDate
271
+ [value.year, value.month, value.day, value.hour, value.minute, value.second, 0]
272
+ else
273
+ [value.year, value.month, value.day, value.hour, value.min, value.sec, 0]
274
+ end
275
+ # code from Time.time_with_datetime_fallback
276
+ begin
277
+ Time.send(Base.default_timezone, year, month, day, hour, min, sec, usec)
278
+ rescue
279
+ offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
280
+ ::DateTime.civil(year, month, day, hour, min, sec, offset)
281
+ end
282
+ end
283
+ end
284
+
285
+ # The OracleEnhancedOCIFactory factors out the code necessary to connect and
286
+ # configure an Oracle/OCI connection.
287
+ class OracleEnhancedOCIFactory #:nodoc:
288
+ def self.new_connection(config)
289
+ # to_s needed if username, password or database is specified as number in database.yml file
290
+ username = config[:username] && config[:username].to_s
291
+ password = config[:password] && config[:password].to_s
292
+ database = config[:database] && config[:database].to_s
293
+ schema = config[:schema] && config[:schema].to_s
294
+ host, port = config[:host], config[:port]
295
+ privilege = config[:privilege] && config[:privilege].to_sym
296
+ async = config[:allow_concurrency]
297
+ prefetch_rows = config[:prefetch_rows] || 100
298
+ cursor_sharing = config[:cursor_sharing]
299
+ # get session time_zone from configuration or from TZ environment variable
300
+ time_zone = config[:time_zone] || ENV["TZ"]
301
+
302
+ # using a connection string via DATABASE_URL
303
+ connection_string = if host == "connection-string"
304
+ database
305
+ # connection using host, port and database name
306
+ elsif host || port
307
+ host ||= "localhost"
308
+ host = "[#{host}]" if host =~ /^[^\[].*:/ # IPv6
309
+ port ||= 1521
310
+ database = "/#{database}" unless database.match(/^\//)
311
+ "//#{host}:#{port}#{database}"
312
+ # if no host is specified then assume that
313
+ # database parameter is TNS alias or TNS connection string
314
+ else
315
+ database
316
+ end
317
+ OCI8.properties[:tcp_keepalive] = true
318
+ begin
319
+ OCI8.properties[:tcp_keepalive_time] = 600
320
+ rescue NotImplementedError
321
+ end
322
+ conn = OCI8.new username, password, connection_string, privilege
323
+ conn.autocommit = true
324
+ conn.non_blocking = true if async
325
+ conn.prefetch_rows = prefetch_rows
326
+ conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil if cursor_sharing
327
+ if ActiveRecord::Base.default_timezone == :local
328
+ conn.exec "alter session set time_zone = '#{time_zone}'" unless time_zone.blank?
329
+ elsif ActiveRecord::Base.default_timezone == :utc
330
+ conn.exec "alter session set time_zone = '+00:00'"
331
+ end
332
+ conn.exec "alter session set current_schema = #{schema}" unless schema.blank?
333
+
334
+ # Initialize NLS parameters
335
+ OracleEnhancedAdapter::DEFAULT_NLS_PARAMETERS.each do |key, default_value|
336
+ value = config[key] || ENV[key.to_s.upcase] || default_value
337
+ if value
338
+ conn.exec "alter session set #{key} = '#{value}'"
339
+ end
340
+ end
341
+ conn
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ class OCI8 #:nodoc:
349
+ def describe(name)
350
+ info = describe_table(name.to_s)
351
+ raise %Q{"DESC #{name}" failed} if info.nil?
352
+ if info.respond_to?(:obj_link) && info.obj_link
353
+ [info.obj_schema, info.obj_name, "@" + info.obj_link]
354
+ else
355
+ [info.obj_schema, info.obj_name]
356
+ end
357
+ end
358
+ end
359
+
360
+ # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
361
+ # reset functionality. If a call to #exec fails, and autocommit is turned on
362
+ # (ie., we're not in the middle of a longer transaction), it will
363
+ # automatically reconnect and try again. If autocommit is turned off,
364
+ # this would be dangerous (as the earlier part of the implied transaction
365
+ # may have failed silently if the connection died) -- so instead the
366
+ # connection is marked as dead, to be reconnected on it's next use.
367
+ #:stopdoc:
368
+ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
369
+ attr_accessor :active #:nodoc:
370
+ alias :active? :active #:nodoc:
371
+
372
+ cattr_accessor :auto_retry
373
+ class << self
374
+ alias :auto_retry? :auto_retry #:nodoc:
375
+ end
376
+ @@auto_retry = false
377
+
378
+ def initialize(config, factory) #:nodoc:
379
+ @active = true
380
+ @config = config
381
+ @factory = factory
382
+ @connection = @factory.new_connection @config
383
+ super @connection
384
+ end
385
+
386
+ # Checks connection, returns true if active. Note that ping actively
387
+ # checks the connection, while #active? simply returns the last
388
+ # known state.
389
+ def ping #:nodoc:
390
+ @connection.exec("select 1 from dual") { |r| nil }
391
+ @active = true
392
+ rescue
393
+ @active = false
394
+ raise
395
+ end
396
+
397
+ # Resets connection, by logging off and creating a new connection.
398
+ def reset! #:nodoc:
399
+ logoff rescue nil
400
+ begin
401
+ @connection = @factory.new_connection @config
402
+ __setobj__ @connection
403
+ @active = true
404
+ rescue
405
+ @active = false
406
+ raise
407
+ end
408
+ end
409
+
410
+ # ORA-00028: your session has been killed
411
+ # ORA-01012: not logged on
412
+ # ORA-03113: end-of-file on communication channel
413
+ # ORA-03114: not connected to ORACLE
414
+ # ORA-03135: connection lost contact
415
+ LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ] #:nodoc:
416
+
417
+ # Adds auto-recovery functionality.
418
+ #
419
+ # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
420
+ def exec(sql, *bindvars, &block) #:nodoc:
421
+ should_retry = self.class.auto_retry? && autocommit?
422
+
423
+ begin
424
+ @connection.exec(sql, *bindvars, &block)
425
+ rescue OCIException => e
426
+ raise unless e.is_a?(OCIError) && LOST_CONNECTION_ERROR_CODES.include?(e.code)
427
+ @active = false
428
+ raise unless should_retry
429
+ should_retry = false
430
+ reset! rescue nil
431
+ retry
432
+ end
433
+ end
434
+ end
435
+ #:startdoc: