rsim-activerecord-oracle_enhanced-adapter 1.1.9.90 → 1.1.9.91

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.
data/Manifest.txt CHANGED
@@ -4,13 +4,17 @@ README.txt
4
4
  lib/active_record/connection_adapters/emulation/oracle_adapter.rb
5
5
  lib/active_record/connection_adapters/oracle_enhanced.rake
6
6
  lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
7
+ lib/active_record/connection_adapters/oracle_enhanced_connection.rb
7
8
  lib/active_record/connection_adapters/oracle_enhanced_cpk.rb
8
9
  lib/active_record/connection_adapters/oracle_enhanced_dirty.rb
10
+ lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb
11
+ lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb
9
12
  lib/active_record/connection_adapters/oracle_enhanced_procedures.rb
10
13
  lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb
11
14
  lib/active_record/connection_adapters/oracle_enhanced_tasks.rb
12
15
  lib/active_record/connection_adapters/oracle_enhanced_version.rb
13
16
  spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb
17
+ spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb
14
18
  spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb
15
19
  spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb
16
20
  spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb
@@ -0,0 +1,71 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ # interface independent methods
4
+ class OracleEnhancedConnection
5
+
6
+ def self.create(config)
7
+ case ORACLE_ENHANCED_CONNECTION
8
+ when :oci
9
+ OracleEnhancedOCIConnection.new(config)
10
+ when :jdbc
11
+ OracleEnhancedJDBCConnection.new(config)
12
+ else
13
+ nil
14
+ end
15
+ end
16
+
17
+ attr_reader :raw_connection
18
+
19
+ # Oracle column names by default are case-insensitive, but treated as upcase;
20
+ # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
21
+ # their column names when creating Oracle tables, which makes then case-sensitive.
22
+ # I don't know anybody who does this, but we'll handle the theoretical case of a
23
+ # camelCase column name. I imagine other dbs handle this different, since there's a
24
+ # unit test that's currently failing test_oci.
25
+ def oracle_downcase(column_name)
26
+ column_name =~ /[a-z]/ ? column_name : column_name.downcase
27
+ end
28
+
29
+ private
30
+
31
+ # Returns a record hash with the column names as keys and column values
32
+ # as values.
33
+ def select_one(sql)
34
+ result = select(sql)
35
+ result.first if result
36
+ end
37
+
38
+ # Returns a single value from a record
39
+ def select_value(sql)
40
+ if result = select_one(sql)
41
+ result.values.first
42
+ end
43
+ end
44
+
45
+ # Returns an array of the values of the first column in a select:
46
+ # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
47
+ def select_values(sql)
48
+ result = select(sql)
49
+ result.map { |r| r.values.first }
50
+ end
51
+
52
+
53
+ end
54
+
55
+ class OracleEnhancedConnectionException < StandardError
56
+ end
57
+
58
+ end
59
+ end
60
+
61
+ # if MRI or YARV
62
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
63
+ ORACLE_ENHANCED_CONNECTION = :oci
64
+ require 'active_record/connection_adapters/oracle_enhanced_oci_connection'
65
+ # if JRuby
66
+ elsif RUBY_ENGINE == 'jruby'
67
+ ORACLE_ENHANCED_CONNECTION = :jdbc
68
+ require 'active_record/connection_adapters/oracle_enhanced_jdbc_connection'
69
+ else
70
+ raise "Unsupported Ruby engine #{RUBY_ENGINE}"
71
+ end
@@ -0,0 +1,345 @@
1
+ begin
2
+ require "java"
3
+ require "jruby"
4
+ # Adds JRuby classloader to current thread classloader - as a result ojdbc14.jar should not be in $JRUBY_HOME/lib
5
+ java.lang.Thread.currentThread.setContextClassLoader(JRuby.runtime.jruby_class_loader)
6
+
7
+ ojdbc_jar = "ojdbc14.jar"
8
+ if ojdbc_jar_path = ENV["PATH"].split(/[:;]/).find{|d| File.exists?(File.join(d,ojdbc_jar))}
9
+ require File.join(ojdbc_jar_path,ojdbc_jar)
10
+ end
11
+ # import java.sql.Statement
12
+ # import java.sql.Connection
13
+ # import java.sql.SQLException
14
+ # import java.sql.Types
15
+ # import java.sql.DriverManager
16
+ java.sql.DriverManager.registerDriver Java::oracle.jdbc.driver.OracleDriver.new
17
+
18
+ rescue LoadError, NameError
19
+ # JDBC driver is unavailable.
20
+ error_message = "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. "+
21
+ "Please install ojdbc14.jar library."
22
+ if defined?(RAILS_DEFAULT_LOGGER)
23
+ RAILS_DEFAULT_LOGGER.error error_message
24
+ else
25
+ STDERR.puts error_message
26
+ end
27
+ raise LoadError
28
+ end
29
+
30
+
31
+ module ActiveRecord
32
+ module ConnectionAdapters
33
+
34
+ # JDBC database interface for JRuby
35
+ class OracleEnhancedJDBCConnection < OracleEnhancedConnection
36
+
37
+ attr_accessor :active
38
+ alias :active? :active
39
+
40
+ attr_accessor :auto_retry
41
+ alias :auto_retry? :auto_retry
42
+ @auto_retry = false
43
+
44
+ def initialize(config)
45
+ @active = true
46
+ @config = config
47
+ new_connection(@config)
48
+ end
49
+
50
+ def new_connection(config)
51
+ username, password, database = config[:username].to_s, config[:password].to_s, config[:database].to_s
52
+ privilege = config[:privilege] && config[:privilege].to_s
53
+ host, port = config[:host], config[:port]
54
+
55
+ url = config[:url] || "jdbc:oracle:thin:@#{host || 'localhost'}:#{port || 1521}:#{database || 'XE'}"
56
+
57
+ prefetch_rows = config[:prefetch_rows] || 100
58
+ cursor_sharing = config[:cursor_sharing] || 'similar'
59
+
60
+ properties = java.util.Properties.new
61
+ properties.put("user", username)
62
+ properties.put("password", password)
63
+ properties.put("defaultRowPrefetch", "#{prefetch_rows}") if prefetch_rows
64
+ properties.put("internal_logon", privilege) if privilege
65
+
66
+ @raw_connection = java.sql.DriverManager.getConnection(url, properties)
67
+ exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
68
+ exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} # rescue nil
69
+ exec "alter session set cursor_sharing = #{cursor_sharing}" # rescue nil
70
+ self.autocommit = true
71
+
72
+ # Set session time zone to current time zone
73
+ @raw_connection.setSessionTimeZone(java.util.TimeZone.default.getID)
74
+
75
+ # default schema owner
76
+ @owner = username.upcase
77
+
78
+ @raw_connection
79
+ end
80
+
81
+
82
+ def logoff
83
+ @active = false
84
+ @raw_connection.close
85
+ true
86
+ rescue
87
+ false
88
+ end
89
+
90
+ def commit
91
+ @raw_connection.commit
92
+ end
93
+
94
+ def rollback
95
+ @raw_connection.rollback
96
+ end
97
+
98
+ def autocommit?
99
+ @raw_connection.getAutoCommit
100
+ end
101
+
102
+ def autocommit=(value)
103
+ @raw_connection.setAutoCommit(value)
104
+ end
105
+
106
+ # Checks connection, returns true if active. Note that ping actively
107
+ # checks the connection, while #active? simply returns the last
108
+ # known state.
109
+ def ping
110
+ exec_no_retry("select 1 from dual")
111
+ @active = true
112
+ rescue NativeException => e
113
+ @active = false
114
+ if e.message =~ /^java\.sql\.SQLException/
115
+ raise OracleEnhancedConnectionException, e.message
116
+ else
117
+ raise
118
+ end
119
+ end
120
+
121
+ # Resets connection, by logging off and creating a new connection.
122
+ def reset!
123
+ logoff rescue nil
124
+ begin
125
+ new_connection(@config)
126
+ @active = true
127
+ rescue NativeException => e
128
+ @active = false
129
+ if e.message =~ /^java\.sql\.SQLException/
130
+ raise OracleEnhancedConnectionException, e.message
131
+ else
132
+ raise
133
+ end
134
+ end
135
+ end
136
+
137
+ # mark connection as dead if connection lost
138
+ def with_retry(&block)
139
+ should_retry = auto_retry? && autocommit?
140
+ begin
141
+ yield if block_given?
142
+ rescue NativeException => e
143
+ raise unless e.message =~ /^java\.sql\.SQLException: (Closed Connection|Io exception:|No more data to read from socket)/
144
+ @active = false
145
+ raise unless should_retry
146
+ should_retry = false
147
+ reset! rescue nil
148
+ retry
149
+ end
150
+ end
151
+
152
+ def exec(sql)
153
+ with_retry do
154
+ exec_no_retry(sql)
155
+ end
156
+ end
157
+
158
+ def exec_no_retry(sql)
159
+ cs = prepare_call(sql)
160
+ cs.execute
161
+ true
162
+ ensure
163
+ cs.close rescue nil
164
+ end
165
+
166
+ def select(sql, name = nil)
167
+ with_retry do
168
+ select_no_retry(sql, name)
169
+ end
170
+ end
171
+
172
+ def select_no_retry(sql, name = nil)
173
+ stmt = prepare_statement(sql)
174
+ rset = stmt.executeQuery
175
+ metadata = rset.getMetaData
176
+ column_count = metadata.getColumnCount
177
+ cols = (1..column_count).map do |i|
178
+ oracle_downcase(metadata.getColumnName(i))
179
+ end
180
+ col_types = (1..column_count).map do |i|
181
+ metadata.getColumnTypeName(i)
182
+ end
183
+
184
+ rows = []
185
+
186
+ while rset.next
187
+ hash = Hash.new
188
+
189
+ cols.each_with_index do |col, i0|
190
+ i = i0 + 1
191
+ hash[col] =
192
+ case column_type = col_types[i0]
193
+ when /CLOB/
194
+ name == 'Writable Large Object' ? rset.getClob(i) : get_ruby_value_from_result_set(rset, i, column_type)
195
+ when /BLOB/
196
+ name == 'Writable Large Object' ? rset.getBlob(i) : get_ruby_value_from_result_set(rset, i, column_type)
197
+ when 'DATE'
198
+ t = get_ruby_value_from_result_set(rset, i, column_type)
199
+ # RSI: added emulate_dates_by_column_name functionality
200
+ # if emulate_dates_by_column_name && self.class.is_date_column?(col)
201
+ # d.to_date
202
+ # elsif
203
+ if t && OracleEnhancedAdapter.emulate_dates && (t.hour == 0 && t.min == 0 && t.sec == 0)
204
+ t.to_date
205
+ else
206
+ # JRuby Time supports time before year 1900 therefore now need to fall back to DateTime
207
+ t
208
+ end
209
+ # RSI: added emulate_integers_by_column_name functionality
210
+ when "NUMBER"
211
+ n = get_ruby_value_from_result_set(rset, i, column_type)
212
+ if n && n.is_a?(Float) && OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
213
+ n.to_i
214
+ else
215
+ n
216
+ end
217
+ else
218
+ get_ruby_value_from_result_set(rset, i, column_type)
219
+ end unless col == 'raw_rnum_'
220
+ end
221
+
222
+ rows << hash
223
+ end
224
+
225
+ rows
226
+ ensure
227
+ rset.close rescue nil
228
+ stmt.close rescue nil
229
+ end
230
+
231
+ def write_lob(lob, value, is_binary = false)
232
+ if is_binary
233
+ lob.setBytes(1, value.to_java_bytes)
234
+ else
235
+ lob.setString(1,value)
236
+ end
237
+ end
238
+
239
+ def describe(name)
240
+ real_name = OracleEnhancedAdapter.valid_table_name?(name) ? name.upcase : name
241
+ if name.include?('.')
242
+ table_owner, table_name = real_name.split('.')
243
+ else
244
+ table_owner, table_name = @owner, real_name
245
+ end
246
+ sql = <<-SQL
247
+ SELECT owner, table_name, 'TABLE' name_type
248
+ FROM all_tables
249
+ WHERE owner = '#{table_owner}'
250
+ AND table_name = '#{table_name}'
251
+ UNION ALL
252
+ SELECT owner, view_name table_name, 'VIEW' name_type
253
+ FROM all_views
254
+ WHERE owner = '#{table_owner}'
255
+ AND view_name = '#{table_name}'
256
+ UNION ALL
257
+ SELECT table_owner, table_name, 'SYNONYM' name_type
258
+ FROM all_synonyms
259
+ WHERE owner = '#{table_owner}'
260
+ AND synonym_name = '#{table_name}'
261
+ UNION ALL
262
+ SELECT table_owner, table_name, 'SYNONYM' name_type
263
+ FROM all_synonyms
264
+ WHERE owner = 'PUBLIC'
265
+ AND synonym_name = '#{name.upcase}'
266
+ SQL
267
+ if result = select_one(sql)
268
+ case result['name_type']
269
+ when 'SYNONYM'
270
+ describe("#{result['owner']}.#{result['table_name']}")
271
+ else
272
+ [result['owner'], result['table_name']]
273
+ end
274
+ else
275
+ raise OracleEnhancedConnectionException, %Q{"DESC #{name}" failed; does it exist?}
276
+ end
277
+ end
278
+
279
+
280
+ private
281
+
282
+ def prepare_statement(sql)
283
+ @raw_connection.prepareStatement(sql)
284
+ end
285
+
286
+ def prepare_call(sql, *bindvars)
287
+ @raw_connection.prepareCall(sql)
288
+ end
289
+
290
+ def get_ruby_value_from_result_set(rset, i, type_name)
291
+ case type_name
292
+ when "CHAR", "VARCHAR2"
293
+ rset.getString(i)
294
+ when "CLOB"
295
+ ora_value_to_ruby_value(rset.getClob(i))
296
+ when "BLOB"
297
+ ora_value_to_ruby_value(rset.getBlob(i))
298
+ when "NUMBER"
299
+ d = rset.getBigDecimal(i)
300
+ if d.nil?
301
+ nil
302
+ elsif d.scale == 0
303
+ d.longValue
304
+ else
305
+ d.doubleValue
306
+ end
307
+ when "DATE", /^TIMESTAMP/
308
+ ts = rset.getTimestamp(i)
309
+ ts && Time.at(ts.getTime/1000)
310
+ else
311
+ nil
312
+ end
313
+ end
314
+
315
+ def ora_value_to_ruby_value(val)
316
+ case val
317
+ when Float, BigDecimal
318
+ ora_number_to_ruby_number(val)
319
+ when ::Java::OracleSql::CLOB
320
+ if val.isEmptyLob
321
+ nil
322
+ else
323
+ val.getSubString(1, val.length)
324
+ end
325
+ when ::Java::OracleSql::BLOB
326
+ if val.isEmptyLob
327
+ nil
328
+ else
329
+ String.from_java_bytes(val.getBytes(1, val.length))
330
+ end
331
+ else
332
+ val
333
+ end
334
+ end
335
+
336
+ def ora_number_to_ruby_number(num)
337
+ num.to_i == num.to_f ? num.to_i : num.to_f
338
+ end
339
+
340
+
341
+ end
342
+
343
+ end
344
+ end
345
+
@@ -0,0 +1,322 @@
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)
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
+ name == 'Writable Large Object' ? row[i]: row[i].read
98
+ when OraDate
99
+ d = row[i]
100
+ # RSI: added emulate_dates_by_column_name functionality
101
+ # if emulate_dates_by_column_name && self.class.is_date_column?(col)
102
+ # d.to_date
103
+ # elsif
104
+ if OracleEnhancedAdapter.emulate_dates && (d.hour == 0 && d.minute == 0 && d.second == 0)
105
+ d.to_date
106
+ else
107
+ # see string_to_time; Time overflowing to DateTime, respecting the default timezone
108
+ time_array = [d.year, d.month, d.day, d.hour, d.minute, d.second]
109
+ begin
110
+ Time.send(Base.default_timezone, *time_array)
111
+ rescue
112
+ zone_offset = if Base.default_timezone == :local then DateTime.now.offset else 0 end
113
+ # Append zero calendar reform start to account for dates skipped by calendar reform
114
+ DateTime.new(*time_array[0..5] << zone_offset << 0) rescue nil
115
+ end
116
+ end
117
+ # RSI: added emulate_integers_by_column_name functionality
118
+ when Float
119
+ n = row[i]
120
+ if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
121
+ n.to_i
122
+ else
123
+ n
124
+ end
125
+ else row[i]
126
+ end unless col == 'raw_rnum_'
127
+ end
128
+
129
+ rows << hash
130
+ end
131
+
132
+ rows
133
+ ensure
134
+ cursor.close if cursor
135
+ end
136
+
137
+ def write_lob(lob, value, is_binary = false)
138
+ lob.write value
139
+ end
140
+
141
+ def describe(name)
142
+ quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
143
+ @raw_connection.describe(quoted_name)
144
+ rescue OCIException => e
145
+ raise OracleEnhancedConnectionException, e.message
146
+ end
147
+
148
+ end
149
+
150
+ # The OracleEnhancedOCIFactory factors out the code necessary to connect and
151
+ # configure an Oracle/OCI connection.
152
+ class OracleEnhancedOCIFactory #:nodoc:
153
+ def self.new_connection(config)
154
+ username, password, database = config[:username].to_s, config[:password].to_s, config[:database].to_s
155
+ privilege = config[:privilege] && config[:privilege].to_sym
156
+ async = config[:allow_concurrency]
157
+ prefetch_rows = config[:prefetch_rows] || 100
158
+ cursor_sharing = config[:cursor_sharing] || 'similar'
159
+
160
+ conn = OCI8.new username, password, database, privilege
161
+ conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
162
+ conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
163
+ conn.autocommit = true
164
+ conn.non_blocking = true if async
165
+ conn.prefetch_rows = prefetch_rows
166
+ conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
167
+ conn
168
+ end
169
+ end
170
+
171
+
172
+ end
173
+ end
174
+
175
+
176
+
177
+ class OCI8 #:nodoc:
178
+
179
+ class Cursor #:nodoc:
180
+ if method_defined? :define_a_column
181
+ # This OCI8 patch is required with the ruby-oci8 1.0.x or lower.
182
+ # Set OCI8::BindType::Mapping[] to change the column type
183
+ # when using ruby-oci8 2.0.
184
+
185
+ alias :enhanced_define_a_column_pre_ar :define_a_column
186
+ def define_a_column(i)
187
+ case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
188
+ when 8; @stmt.defineByPos(i, String, 65535) # Read LONG values
189
+ when 187; @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
190
+ when 108
191
+ if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
192
+ @stmt.defineByPos(i, String, 65535)
193
+ else
194
+ raise 'unsupported datatype'
195
+ end
196
+ else enhanced_define_a_column_pre_ar i
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ if OCI8.public_method_defined?(:describe_table)
203
+ # ruby-oci8 2.0 or upper
204
+
205
+ def describe(name)
206
+ info = describe_table(name.to_s)
207
+ raise %Q{"DESC #{name}" failed} if info.nil?
208
+ [info.obj_schema, info.obj_name]
209
+ end
210
+ else
211
+ # ruby-oci8 1.0.x or lower
212
+
213
+ # missing constant from oci8 < 0.1.14
214
+ OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
215
+
216
+ # Uses the describeAny OCI call to find the target owner and table_name
217
+ # indicated by +name+, parsing through synonynms as necessary. Returns
218
+ # an array of [owner, table_name].
219
+ def describe(name)
220
+ @desc ||= @@env.alloc(OCIDescribe)
221
+ @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
222
+ do_ocicall(@ctx) { @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) } rescue raise %Q{"DESC #{name}" failed; does it exist?}
223
+ info = @desc.attrGet(OCI_ATTR_PARAM)
224
+
225
+ case info.attrGet(OCI_ATTR_PTYPE)
226
+ when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
227
+ owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
228
+ table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
229
+ [owner, table_name]
230
+ when OCI_PTYPE_SYN
231
+ schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
232
+ name = info.attrGet(OCI_ATTR_NAME)
233
+ describe(schema + '.' + name)
234
+ else raise %Q{"DESC #{name}" failed; not a table or view.}
235
+ end
236
+ end
237
+ end
238
+
239
+ end
240
+
241
+ # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
242
+ # reset functionality. If a call to #exec fails, and autocommit is turned on
243
+ # (ie., we're not in the middle of a longer transaction), it will
244
+ # automatically reconnect and try again. If autocommit is turned off,
245
+ # this would be dangerous (as the earlier part of the implied transaction
246
+ # may have failed silently if the connection died) -- so instead the
247
+ # connection is marked as dead, to be reconnected on it's next use.
248
+ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
249
+ attr_accessor :active
250
+ alias :active? :active
251
+
252
+ cattr_accessor :auto_retry
253
+ class << self
254
+ alias :auto_retry? :auto_retry
255
+ end
256
+ @@auto_retry = false
257
+
258
+ def initialize(config, factory)
259
+ @active = true
260
+ @config = config
261
+ @factory = factory
262
+ @connection = @factory.new_connection @config
263
+ super @connection
264
+ end
265
+
266
+ # Checks connection, returns true if active. Note that ping actively
267
+ # checks the connection, while #active? simply returns the last
268
+ # known state.
269
+ def ping
270
+ @connection.exec("select 1 from dual") { |r| nil }
271
+ @active = true
272
+ rescue
273
+ @active = false
274
+ raise
275
+ end
276
+
277
+ # Resets connection, by logging off and creating a new connection.
278
+ def reset!
279
+ logoff rescue nil
280
+ begin
281
+ @connection = @factory.new_connection @config
282
+ __setobj__ @connection
283
+ @active = true
284
+ rescue
285
+ @active = false
286
+ raise
287
+ end
288
+ end
289
+
290
+ # ORA-00028: your session has been killed
291
+ # ORA-01012: not logged on
292
+ # ORA-03113: end-of-file on communication channel
293
+ # ORA-03114: not connected to ORACLE
294
+ # ORA-03135: connection lost contact
295
+ LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ]
296
+
297
+ # Adds auto-recovery functionality.
298
+ #
299
+ # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
300
+ def exec(sql, *bindvars, &block)
301
+ should_retry = self.class.auto_retry? && autocommit?
302
+
303
+ begin
304
+ @connection.exec(sql, *bindvars, &block)
305
+ rescue OCIException => e
306
+ raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
307
+ @active = false
308
+ raise unless should_retry
309
+ should_retry = false
310
+ reset! rescue nil
311
+ retry
312
+ end
313
+ end
314
+
315
+ # RSI: otherwise not working in Ruby 1.9.1
316
+ if RUBY_VERSION =~ /^1\.9/
317
+ def describe(name)
318
+ @connection.describe(name)
319
+ end
320
+ end
321
+
322
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{activerecord-oracle_enhanced-adapter}
5
- s.version = "1.1.9.90"
5
+ s.version = "1.1.9.91"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Raimonds Simanovskis"]
@@ -14,14 +14,18 @@ Gem::Specification.new do |s|
14
14
  "lib/active_record/connection_adapters/emulation/oracle_adapter.rb",
15
15
  "lib/active_record/connection_adapters/oracle_enhanced.rake",
16
16
  "lib/active_record/connection_adapters/oracle_enhanced_adapter.rb",
17
+ "lib/active_record/connection_adapters/oracle_enhanced_connection.rb",
17
18
  "lib/active_record/connection_adapters/oracle_enhanced_cpk.rb",
18
19
  "lib/active_record/connection_adapters/oracle_enhanced_dirty.rb",
20
+ "lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb",
21
+ "lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb",
19
22
  "lib/active_record/connection_adapters/oracle_enhanced_procedures.rb",
20
23
  "lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb",
21
24
  "lib/active_record/connection_adapters/oracle_enhanced_tasks.rb",
22
25
  "lib/active_record/connection_adapters/oracle_enhanced_version.rb",
23
26
  "oracle-enhanced.gemspec",
24
27
  "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb",
28
+ "spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb",
25
29
  "spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb",
26
30
  "spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb",
27
31
  "spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb",
@@ -0,0 +1,166 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ describe "OracleEnhancedConnection create connection" do
4
+
5
+ before(:all) do
6
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
7
+ end
8
+
9
+ before(:each) do
10
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS) unless @conn.active?
11
+ end
12
+
13
+ after(:all) do
14
+ @conn.logoff if @conn.active?
15
+ end
16
+
17
+ it "should create new connection" do
18
+ @conn.should be_active
19
+ end
20
+
21
+ it "should ping active connection" do
22
+ @conn.ping.should be_true
23
+ end
24
+
25
+ it "should not ping inactive connection" do
26
+ @conn.logoff
27
+ lambda { @conn.ping }.should raise_error(ActiveRecord::ConnectionAdapters::OracleEnhancedConnectionException)
28
+ end
29
+
30
+ it "should reset active connection" do
31
+ @conn.reset!
32
+ @conn.should be_active
33
+ end
34
+
35
+ it "should be in autocommit mode after connection" do
36
+ @conn.should be_autocommit
37
+ end
38
+
39
+ end
40
+
41
+ describe "OracleEnhancedConnection SQL execution" do
42
+
43
+ before(:all) do
44
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
45
+ end
46
+
47
+ before(:each) do
48
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS) unless @conn.active?
49
+ end
50
+
51
+ after(:all) do
52
+ @conn.logoff if @conn.active?
53
+ end
54
+
55
+ it "should execute SQL statement" do
56
+ @conn.exec("SELECT * FROM dual").should_not be_nil
57
+ end
58
+
59
+ it "should execute SQL select" do
60
+ @conn.select("SELECT * FROM dual").should == [{'dummy' => 'X'}]
61
+ end
62
+
63
+ end
64
+
65
+ describe "OracleEnhancedConnection auto reconnection" do
66
+
67
+ before(:all) do
68
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
69
+ @conn = ActiveRecord::Base.connection.instance_variable_get("@connection")
70
+ @sys_conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(SYS_CONNECTION_PARAMS)
71
+ end
72
+
73
+ before(:each) do
74
+ ActiveRecord::Base.connection.reconnect! unless @conn.active?
75
+ end
76
+
77
+ after(:all) do
78
+ ActiveRecord::Base.connection.disconnect! if @conn.active?
79
+ end
80
+
81
+ def kill_current_session
82
+ audsid = @conn.select("SELECT userenv('sessionid') audsid FROM dual").first['audsid']
83
+ sid_serial = @sys_conn.select("SELECT s.sid||','||s.serial# sid_serial
84
+ FROM v$session s
85
+ WHERE audsid = '#{audsid}'").first['sid_serial']
86
+ @sys_conn.exec "ALTER SYSTEM KILL SESSION '#{sid_serial}' IMMEDIATE"
87
+ end
88
+
89
+ it "should reconnect and execute SQL statement if connection is lost and auto retry is enabled" do
90
+ # @conn.auto_retry = true
91
+ ActiveRecord::Base.connection.auto_retry = true
92
+ kill_current_session
93
+ @conn.exec("SELECT * FROM dual").should_not be_nil
94
+ end
95
+
96
+ it "should not reconnect and execute SQL statement if connection is lost and auto retry is disabled" do
97
+ # @conn.auto_retry = false
98
+ ActiveRecord::Base.connection.auto_retry = false
99
+ kill_current_session
100
+ lambda { @conn.exec("SELECT * FROM dual") }.should raise_error
101
+ end
102
+
103
+ it "should reconnect and execute SQL select if connection is lost and auto retry is enabled" do
104
+ # @conn.auto_retry = true
105
+ ActiveRecord::Base.connection.auto_retry = true
106
+ kill_current_session
107
+ @conn.select("SELECT * FROM dual").should == [{'dummy' => 'X'}]
108
+ end
109
+
110
+ it "should not reconnect and execute SQL select if connection is lost and auto retry is disabled" do
111
+ # @conn.auto_retry = false
112
+ ActiveRecord::Base.connection.auto_retry = false
113
+ kill_current_session
114
+ lambda { @conn.select("SELECT * FROM dual") }.should raise_error
115
+ end
116
+
117
+ end
118
+
119
+ describe "OracleEnhancedConnection describe table" do
120
+
121
+ before(:all) do
122
+ @conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
123
+ @owner = CONNECTION_PARAMS[:username].upcase
124
+ end
125
+
126
+ after(:all) do
127
+ @conn.logoff if @conn.active?
128
+ end
129
+
130
+ it "should describe existing table" do
131
+ @conn.exec "CREATE TABLE test_employees (first_name VARCHAR2(20))" rescue nil
132
+ @conn.describe("test_employees").should == [@owner, "TEST_EMPLOYEES"]
133
+ @conn.exec "DROP TABLE test_employees" rescue nil
134
+ end
135
+
136
+ it "should not describe non-existing table" do
137
+ lambda { @conn.describe("test_xxx") }.should raise_error(ActiveRecord::ConnectionAdapters::OracleEnhancedConnectionException)
138
+ end
139
+
140
+ it "should describe table in other schema" do
141
+ @conn.describe("sys.dual").should == ["SYS", "DUAL"]
142
+ end
143
+
144
+ it "should describe existing view" do
145
+ @conn.exec "CREATE TABLE test_employees (first_name VARCHAR2(20))" rescue nil
146
+ @conn.exec "CREATE VIEW test_employees_v AS SELECT * FROM test_employees" rescue nil
147
+ @conn.describe("test_employees_v").should == [@owner, "TEST_EMPLOYEES_V"]
148
+ @conn.exec "DROP VIEW test_employees_v" rescue nil
149
+ @conn.exec "DROP TABLE test_employees" rescue nil
150
+ end
151
+
152
+ it "should describe view in other schema" do
153
+ @conn.describe("sys.v_$version").should == ["SYS", "V_$VERSION"]
154
+ end
155
+
156
+ it "should describe existing private synonym" do
157
+ @conn.exec "CREATE SYNONYM test_dual FOR sys.dual" rescue nil
158
+ @conn.describe("test_dual").should == ["SYS", "DUAL"]
159
+ @conn.exec "DROP SYNONYM test_dual" rescue nil
160
+ end
161
+
162
+ it "should describe existing public synonym" do
163
+ @conn.describe("all_tables").should == ["SYS", "ALL_TABLES"]
164
+ end
165
+
166
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsim-activerecord-oracle_enhanced-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.9.90
4
+ version: 1.1.9.91
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raimonds Simanovskis
@@ -42,14 +42,18 @@ files:
42
42
  - lib/active_record/connection_adapters/emulation/oracle_adapter.rb
43
43
  - lib/active_record/connection_adapters/oracle_enhanced.rake
44
44
  - lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
45
+ - lib/active_record/connection_adapters/oracle_enhanced_connection.rb
45
46
  - lib/active_record/connection_adapters/oracle_enhanced_cpk.rb
46
47
  - lib/active_record/connection_adapters/oracle_enhanced_dirty.rb
48
+ - lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb
49
+ - lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb
47
50
  - lib/active_record/connection_adapters/oracle_enhanced_procedures.rb
48
51
  - lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb
49
52
  - lib/active_record/connection_adapters/oracle_enhanced_tasks.rb
50
53
  - lib/active_record/connection_adapters/oracle_enhanced_version.rb
51
54
  - oracle-enhanced.gemspec
52
55
  - spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb
56
+ - spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb
53
57
  - spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb
54
58
  - spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb
55
59
  - spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb