activerecord-oracle_enhanced-adapter 1.2.1 → 1.2.2

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 (30) hide show
  1. data/History.txt +34 -0
  2. data/README.rdoc +10 -5
  3. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +1 -1
  4. data/lib/active_record/connection_adapters/oracle_enhanced.rake +4 -0
  5. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +534 -170
  6. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +53 -3
  7. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +10 -10
  8. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +3 -3
  9. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +3 -3
  10. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +86 -58
  11. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +105 -68
  12. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +27 -1
  13. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +164 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +122 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +2 -2
  17. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -1
  18. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +230 -455
  19. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +37 -1
  20. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +1 -1
  21. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +6 -2
  22. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +21 -4
  23. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +63 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +1 -1
  25. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +1 -3
  26. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +1 -1
  27. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +255 -0
  28. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +720 -0
  29. data/spec/spec_helper.rb +38 -7
  30. metadata +13 -15
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  # interface independent methods
4
- class OracleEnhancedConnection
4
+ class OracleEnhancedConnection #:nodoc:
5
5
 
6
6
  def self.create(config)
7
7
  case ORACLE_ENHANCED_CONNECTION
@@ -23,9 +23,60 @@ module ActiveRecord
23
23
  # camelCase column name. I imagine other dbs handle this different, since there's a
24
24
  # unit test that's currently failing test_oci.
25
25
  def oracle_downcase(column_name)
26
+ return nil if column_name.nil?
26
27
  column_name =~ /[a-z]/ ? column_name : column_name.downcase
27
28
  end
28
29
 
30
+ # Used always by JDBC connection as well by OCI connection when describing tables over database link
31
+ def describe(name)
32
+ name = name.to_s
33
+ if name.include?('@')
34
+ name, db_link = name.split('@')
35
+ default_owner = select_value("SELECT username FROM all_db_links WHERE db_link = '#{db_link.upcase}'")
36
+ db_link = "@#{db_link}"
37
+ else
38
+ db_link = nil
39
+ default_owner = @owner
40
+ end
41
+ real_name = OracleEnhancedAdapter.valid_table_name?(name) ? name.upcase : name
42
+ if real_name.include?('.')
43
+ table_owner, table_name = real_name.split('.')
44
+ else
45
+ table_owner, table_name = default_owner, real_name
46
+ end
47
+ sql = <<-SQL
48
+ SELECT owner, table_name, 'TABLE' name_type
49
+ FROM all_tables#{db_link}
50
+ WHERE owner = '#{table_owner}'
51
+ AND table_name = '#{table_name}'
52
+ UNION ALL
53
+ SELECT owner, view_name table_name, 'VIEW' name_type
54
+ FROM all_views#{db_link}
55
+ WHERE owner = '#{table_owner}'
56
+ AND view_name = '#{table_name}'
57
+ UNION ALL
58
+ SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
59
+ FROM all_synonyms#{db_link}
60
+ WHERE owner = '#{table_owner}'
61
+ AND synonym_name = '#{table_name}'
62
+ UNION ALL
63
+ SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
64
+ FROM all_synonyms#{db_link}
65
+ WHERE owner = 'PUBLIC'
66
+ AND synonym_name = '#{real_name}'
67
+ SQL
68
+ if result = select_one(sql)
69
+ case result['name_type']
70
+ when 'SYNONYM'
71
+ describe("#{result['owner'] && "#{result['owner']}."}#{result['table_name']}#{db_link}")
72
+ else
73
+ db_link ? [result['owner'], result['table_name'], db_link] : [result['owner'], result['table_name']]
74
+ end
75
+ else
76
+ raise OracleEnhancedConnectionException, %Q{"DESC #{name}" failed; does it exist?}
77
+ end
78
+ end
79
+
29
80
  private
30
81
 
31
82
  # Returns a record hash with the column names as keys and column values
@@ -48,11 +99,10 @@ module ActiveRecord
48
99
  result = select(sql)
49
100
  result.map { |r| r.values.first }
50
101
  end
51
-
52
102
 
53
103
  end
54
104
 
55
- class OracleEnhancedConnectionException < StandardError
105
+ class OracleEnhancedConnectionException < StandardError #:nodoc:
56
106
  end
57
107
 
58
108
  end
@@ -1,7 +1,7 @@
1
1
  require "bigdecimal"
2
2
  unless BigDecimal.instance_methods.include?("to_d")
3
3
  BigDecimal.class_eval do
4
- def to_d
4
+ def to_d #:nodoc:
5
5
  self
6
6
  end
7
7
  end
@@ -9,7 +9,7 @@ end
9
9
 
10
10
  unless Bignum.instance_methods.include?("to_d")
11
11
  Bignum.class_eval do
12
- def to_d
12
+ def to_d #:nodoc:
13
13
  BigDecimal.new(self.to_s)
14
14
  end
15
15
  end
@@ -17,7 +17,7 @@ end
17
17
 
18
18
  unless Fixnum.instance_methods.include?("to_d")
19
19
  Fixnum.class_eval do
20
- def to_d
20
+ def to_d #:nodoc:
21
21
  BigDecimal.new(self.to_s)
22
22
  end
23
23
  end
@@ -30,22 +30,22 @@ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && RUBY_VERSION >= '1.9'
30
30
  require "unicode_utils/upcase"
31
31
  require "unicode_utils/downcase"
32
32
 
33
- module ActiveRecord
34
- module ConnectionAdapters
35
- module OracleEnhancedUnicodeString
36
- def upcase
33
+ module ActiveRecord #:nodoc:
34
+ module ConnectionAdapters #:nodoc:
35
+ module OracleEnhancedUnicodeString #:nodoc:
36
+ def upcase #:nodoc:
37
37
  UnicodeUtils.upcase(self)
38
38
  end
39
39
 
40
- def downcase
40
+ def downcase #:nodoc:
41
41
  UnicodeUtils.downcase(self)
42
42
  end
43
43
  end
44
44
  end
45
45
  end
46
46
 
47
- class String
48
- def mb_chars
47
+ class String #:nodoc:
48
+ def mb_chars #:nodoc:
49
49
  self.extend(ActiveRecord::ConnectionAdapters::OracleEnhancedUnicodeString)
50
50
  self
51
51
  end
@@ -2,13 +2,13 @@ module ActiveRecord #:nodoc:
2
2
  module ConnectionAdapters #:nodoc:
3
3
  module OracleEnhancedCpk #:nodoc:
4
4
 
5
- # This mightn't be in Core, but count(distinct x,y) doesn't work for me
6
- # RSI: return that not supported if composite_primary_keys gem is required
5
+ # This mightn't be in Core, but count(distinct x,y) doesn't work for me.
6
+ # Return that not supported if composite_primary_keys gem is required.
7
7
  def supports_count_distinct? #:nodoc:
8
8
  @supports_count_distinct ||= ! defined?(CompositePrimaryKeys)
9
9
  end
10
10
 
11
- def concat(*columns)
11
+ def concat(*columns) #:nodoc:
12
12
  "(#{columns.join('||')})"
13
13
  end
14
14
 
@@ -2,19 +2,19 @@ module ActiveRecord #:nodoc:
2
2
  module ConnectionAdapters #:nodoc:
3
3
  module OracleEnhancedDirty #:nodoc:
4
4
 
5
- module InstanceMethods
5
+ module InstanceMethods #:nodoc:
6
6
  private
7
7
 
8
8
  def field_changed?(attr, old, value)
9
9
  if column = column_for_attribute(attr)
10
- # RSI: added also :decimal type
10
+ # Added also :decimal type
11
11
  if (column.type == :integer || column.type == :decimal) && column.null && (old.nil? || old == 0) && value.blank?
12
12
  # For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
13
13
  # Hence we don't record it as a change if the value changes from nil to ''.
14
14
  # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
15
15
  # be typecast back to 0 (''.to_i => 0)
16
16
  value = nil
17
- # RSI: Oracle stores empty string '' or empty text (CLOB) as NULL
17
+ # Oracle stores empty string '' or empty text (CLOB) as NULL
18
18
  # therefore need to convert empty string value to nil if old value is nil
19
19
  elsif (column.type == :string || column.type == :text) && column.null && old.nil?
20
20
  value = nil if value == ''
@@ -1,20 +1,28 @@
1
1
  begin
2
2
  require "java"
3
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)
4
+
5
+ # ojdbc14.jar file should be in JRUBY_HOME/lib or should be in ENV['PATH'] or load path
6
6
 
7
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)
8
+
9
+ unless ENV_JAVA['java.class.path'] =~ Regexp.new(ojdbc_jar)
10
+ # Adds JRuby classloader to current thread classloader - as a result ojdbc14.jar should not be in $JRUBY_HOME/lib
11
+ # not necessary anymore for JRuby 1.3
12
+ # java.lang.Thread.currentThread.setContextClassLoader(JRuby.runtime.jruby_class_loader)
13
+
14
+ if ojdbc_jar_path = ENV["PATH"].split(/[:;]/).concat($LOAD_PATH).find{|d| File.exists?(File.join(d,ojdbc_jar))}
15
+ require File.join(ojdbc_jar_path,ojdbc_jar)
16
+ end
10
17
  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
18
+
16
19
  java.sql.DriverManager.registerDriver Java::oracle.jdbc.driver.OracleDriver.new
17
20
 
21
+ # set tns_admin property from TNS_ADMIN environment variable
22
+ if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"]
23
+ java.lang.System.set_property("oracle.net.tns_admin", ENV["TNS_ADMIN"])
24
+ end
25
+
18
26
  rescue LoadError, NameError
19
27
  # JDBC driver is unavailable.
20
28
  error_message = "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. "+
@@ -32,7 +40,7 @@ module ActiveRecord
32
40
  module ConnectionAdapters
33
41
 
34
42
  # JDBC database interface for JRuby
35
- class OracleEnhancedJDBCConnection < OracleEnhancedConnection
43
+ class OracleEnhancedJDBCConnection < OracleEnhancedConnection #:nodoc:
36
44
 
37
45
  attr_accessor :active
38
46
  alias :active? :active
@@ -52,10 +60,17 @@ module ActiveRecord
52
60
  privilege = config[:privilege] && config[:privilege].to_s
53
61
  host, port = config[:host], config[:port]
54
62
 
55
- url = config[:url] || "jdbc:oracle:thin:@#{host || 'localhost'}:#{port || 1521}:#{database || 'XE'}"
63
+ # connection using TNS alias
64
+ if database && !host && !config[:url] && ENV['TNS_ADMIN']
65
+ url = "jdbc:oracle:thin:@#{database || 'XE'}"
66
+ else
67
+ url = config[:url] || "jdbc:oracle:thin:@#{host || 'localhost'}:#{port || 1521}:#{database || 'XE'}"
68
+ end
56
69
 
57
70
  prefetch_rows = config[:prefetch_rows] || 100
58
- cursor_sharing = config[:cursor_sharing] || 'similar'
71
+ cursor_sharing = config[:cursor_sharing] || 'force'
72
+ # by default VARCHAR2 column size will be interpreted as max number of characters (and not bytes)
73
+ nls_length_semantics = config[:nls_length_semantics] || 'CHAR'
59
74
 
60
75
  properties = java.util.Properties.new
61
76
  properties.put("user", username)
@@ -65,8 +80,9 @@ module ActiveRecord
65
80
 
66
81
  @raw_connection = java.sql.DriverManager.getConnection(url, properties)
67
82
  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
83
+ exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'}
84
+ exec "alter session set cursor_sharing = #{cursor_sharing}"
85
+ exec "alter session set nls_length_semantics = '#{nls_length_semantics}'"
70
86
  self.autocommit = true
71
87
 
72
88
  # Set session time zone to current time zone
@@ -159,16 +175,64 @@ module ActiveRecord
159
175
  end
160
176
 
161
177
  def exec_no_retry(sql)
162
- cs = @raw_connection.prepareCall(sql)
163
178
  case sql
164
- when /\A\s*UPDATE/i, /\A\s*INSERT/i, /\A\s*DELETE/i
165
- cs.executeUpdate
179
+ when /\A\s*(UPDATE|INSERT|DELETE)/i
180
+ s = @raw_connection.prepareStatement(sql)
181
+ s.executeUpdate
182
+ # it is safer for CREATE and DROP statements not to use PreparedStatement
183
+ # as it does not allow creation of triggers with :NEW in their definition
184
+ when /\A\s*(CREATE|DROP)/i
185
+ s = @raw_connection.createStatement()
186
+ s.execute(sql)
187
+ true
166
188
  else
167
- cs.execute
189
+ s = @raw_connection.prepareStatement(sql)
190
+ s.execute
168
191
  true
169
192
  end
170
193
  ensure
171
- cs.close rescue nil
194
+ s.close rescue nil
195
+ end
196
+
197
+ def returning_clause(quoted_pk)
198
+ " RETURNING #{quoted_pk} INTO ?"
199
+ end
200
+
201
+ # execute sql with RETURNING ... INTO :insert_id
202
+ # and return :insert_id value
203
+ def exec_with_returning(sql)
204
+ with_retry do
205
+ begin
206
+ # it will always be INSERT statement
207
+
208
+ # TODO: need to investigate why PreparedStatement is giving strange exception "Protocol violation"
209
+ # s = @raw_connection.prepareStatement(sql)
210
+ # s.registerReturnParameter(1, ::Java::oracle.jdbc.OracleTypes::NUMBER)
211
+ # count = s.executeUpdate
212
+ # if count > 0
213
+ # rs = s.getReturnResultSet
214
+ # if rs.next
215
+ # # Assuming that primary key will not be larger as long max value
216
+ # insert_id = rs.getLong(1)
217
+ # rs.wasNull ? nil : insert_id
218
+ # else
219
+ # nil
220
+ # end
221
+ # else
222
+ # nil
223
+ # end
224
+
225
+ # Workaround with CallableStatement
226
+ s = @raw_connection.prepareCall("BEGIN #{sql}; END;")
227
+ s.registerOutParameter(1, java.sql.Types::BIGINT)
228
+ s.execute
229
+ insert_id = s.getLong(1)
230
+ s.wasNull ? nil : insert_id
231
+ ensure
232
+ # rs.close rescue nil
233
+ s.close rescue nil
234
+ end
235
+ end
172
236
  end
173
237
 
174
238
  def select(sql, name = nil, return_column_names = false)
@@ -220,46 +284,10 @@ module ActiveRecord
220
284
  end
221
285
  end
222
286
 
223
- def describe(name)
224
- real_name = OracleEnhancedAdapter.valid_table_name?(name) ? name.to_s.upcase : name.to_s
225
- if real_name.include?('.')
226
- table_owner, table_name = real_name.split('.')
227
- else
228
- table_owner, table_name = @owner, real_name
229
- end
230
- sql = <<-SQL
231
- SELECT owner, table_name, 'TABLE' name_type
232
- FROM all_tables
233
- WHERE owner = '#{table_owner}'
234
- AND table_name = '#{table_name}'
235
- UNION ALL
236
- SELECT owner, view_name table_name, 'VIEW' name_type
237
- FROM all_views
238
- WHERE owner = '#{table_owner}'
239
- AND view_name = '#{table_name}'
240
- UNION ALL
241
- SELECT table_owner, table_name, 'SYNONYM' name_type
242
- FROM all_synonyms
243
- WHERE owner = '#{table_owner}'
244
- AND synonym_name = '#{table_name}'
245
- UNION ALL
246
- SELECT table_owner, table_name, 'SYNONYM' name_type
247
- FROM all_synonyms
248
- WHERE owner = 'PUBLIC'
249
- AND synonym_name = '#{real_name}'
250
- SQL
251
- if result = select_one(sql)
252
- case result['name_type']
253
- when 'SYNONYM'
254
- describe("#{result['owner']}.#{result['table_name']}")
255
- else
256
- [result['owner'], result['table_name']]
257
- end
258
- else
259
- raise OracleEnhancedConnectionException, %Q{"DESC #{name}" failed; does it exist?}
260
- end
287
+ # Return NativeException / java.sql.SQLException error code
288
+ def error_code(exception)
289
+ exception.cause.getErrorCode
261
290
  end
262
-
263
291
 
264
292
  private
265
293
 
@@ -3,7 +3,7 @@ require 'delegate'
3
3
  begin
4
4
  require 'oci8' unless self.class.const_defined? :OCI8
5
5
 
6
- # RSI: added mapping for TIMESTAMP / WITH TIME ZONE / LOCAL TIME ZONE types
6
+ # added mapping for TIMESTAMP / WITH TIME ZONE / LOCAL TIME ZONE types
7
7
  # currently Ruby-OCI8 does not support fractional seconds for timestamps
8
8
  OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP] = OCI8::BindType::OraDate
9
9
  OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_TZ] = OCI8::BindType::OraDate
@@ -24,10 +24,12 @@ module ActiveRecord
24
24
  module ConnectionAdapters
25
25
 
26
26
  # OCI database interface for MRI
27
- class OracleEnhancedOCIConnection < OracleEnhancedConnection
27
+ class OracleEnhancedOCIConnection < OracleEnhancedConnection #:nodoc:
28
28
 
29
29
  def initialize(config)
30
30
  @raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory)
31
+ # default schema owner
32
+ @owner = config[:username].to_s.upcase
31
33
  end
32
34
 
33
35
  def auto_retry
@@ -81,6 +83,21 @@ module ActiveRecord
81
83
  def exec(sql, *bindvars, &block)
82
84
  @raw_connection.exec(sql, *bindvars, &block)
83
85
  end
86
+
87
+ def returning_clause(quoted_pk)
88
+ " RETURNING #{quoted_pk} INTO :insert_id"
89
+ end
90
+
91
+ # execute sql with RETURNING ... INTO :insert_id
92
+ # and return :insert_id value
93
+ def exec_with_returning(sql)
94
+ cursor = @raw_connection.parse(sql)
95
+ cursor.bind_param(':insert_id', nil, Integer)
96
+ cursor.exec
97
+ cursor[':insert_id']
98
+ ensure
99
+ cursor.close rescue nil
100
+ end
84
101
 
85
102
  def select(sql, name = nil, return_column_names = false)
86
103
  cursor = @raw_connection.exec(sql)
@@ -100,59 +117,7 @@ module ActiveRecord
100
117
  hash = column_hash.dup
101
118
 
102
119
  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
120
+ hash[col] = typecast_result_value(row[i], get_lob_value)
156
121
  end
157
122
 
158
123
  rows << hash
@@ -168,12 +133,79 @@ module ActiveRecord
168
133
  end
169
134
 
170
135
  def describe(name)
136
+ # fall back to SELECT based describe if using database link
137
+ return super if name.to_s.include?('@')
171
138
  quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
172
139
  @raw_connection.describe(quoted_name)
173
140
  rescue OCIException => e
174
- raise OracleEnhancedConnectionException, e.message
141
+ # fall back to SELECT which can handle synonyms to database links
142
+ super
143
+ end
144
+
145
+ # Return OCIError error code
146
+ def error_code(exception)
147
+ exception.code
148
+ end
149
+
150
+ private
151
+
152
+ def typecast_result_value(value, get_lob_value)
153
+ case value
154
+ when Fixnum, Bignum
155
+ value
156
+ when String
157
+ value
158
+ when Float
159
+ value == (v_to_i = value.to_i) ? v_to_i : value
160
+ # ruby-oci8 2.0 returns OraNumber if Oracle type is NUMBER
161
+ when OraNumber
162
+ value == (v_to_i = value.to_i) ? v_to_i : BigDecimal.new(value.to_s)
163
+ when OCI8::LOB
164
+ if get_lob_value
165
+ data = value.read
166
+ # In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
167
+ data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && value.is_a?(OCI8::BLOB)
168
+ data
169
+ else
170
+ value
171
+ end
172
+ # ruby-oci8 1.0 returns OraDate
173
+ # ruby-oci8 2.0 returns Time or DateTime
174
+ when OraDate, Time, DateTime
175
+ if OracleEnhancedAdapter.emulate_dates && date_without_time?(value)
176
+ value.to_date
177
+ else
178
+ create_time_with_default_timezone(value)
179
+ end
180
+ else
181
+ value
182
+ end
175
183
  end
176
184
 
185
+ def date_without_time?(value)
186
+ case value
187
+ when OraDate
188
+ value.hour == 0 && value.minute == 0 && value.second == 0
189
+ else
190
+ value.hour == 0 && value.min == 0 && value.sec == 0
191
+ end
192
+ end
193
+
194
+ def create_time_with_default_timezone(value)
195
+ year, month, day, hour, min, sec = case value
196
+ when OraDate
197
+ [value.year, value.month, value.day, value.hour, value.minute, value.second]
198
+ else
199
+ [value.year, value.month, value.day, value.hour, value.min, value.sec]
200
+ end
201
+ # code from Time.time_with_datetime_fallback
202
+ begin
203
+ Time.send(Base.default_timezone, year, month, day, hour, min, sec)
204
+ rescue
205
+ offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
206
+ ::DateTime.civil(year, month, day, hour, min, sec, offset)
207
+ end
208
+ end
177
209
  end
178
210
 
179
211
  # The OracleEnhancedOCIFactory factors out the code necessary to connect and
@@ -184,7 +216,9 @@ module ActiveRecord
184
216
  privilege = config[:privilege] && config[:privilege].to_sym
185
217
  async = config[:allow_concurrency]
186
218
  prefetch_rows = config[:prefetch_rows] || 100
187
- cursor_sharing = config[:cursor_sharing] || 'similar'
219
+ cursor_sharing = config[:cursor_sharing] || 'force'
220
+ # by default VARCHAR2 column size will be interpreted as max number of characters (and not bytes)
221
+ nls_length_semantics = config[:nls_length_semantics] || 'CHAR'
188
222
 
189
223
  conn = OCI8.new username, password, database, privilege
190
224
  conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
@@ -193,6 +227,7 @@ module ActiveRecord
193
227
  conn.non_blocking = true if async
194
228
  conn.prefetch_rows = prefetch_rows
195
229
  conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
230
+ conn.exec "alter session set nls_length_semantics = '#{nls_length_semantics}'"
196
231
  conn
197
232
  end
198
233
  end
@@ -274,17 +309,18 @@ end
274
309
  # this would be dangerous (as the earlier part of the implied transaction
275
310
  # may have failed silently if the connection died) -- so instead the
276
311
  # connection is marked as dead, to be reconnected on it's next use.
312
+ #:stopdoc:
277
313
  class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
278
- attr_accessor :active
279
- alias :active? :active
314
+ attr_accessor :active #:nodoc:
315
+ alias :active? :active #:nodoc:
280
316
 
281
317
  cattr_accessor :auto_retry
282
318
  class << self
283
- alias :auto_retry? :auto_retry
319
+ alias :auto_retry? :auto_retry #:nodoc:
284
320
  end
285
321
  @@auto_retry = false
286
322
 
287
- def initialize(config, factory)
323
+ def initialize(config, factory) #:nodoc:
288
324
  @active = true
289
325
  @config = config
290
326
  @factory = factory
@@ -295,7 +331,7 @@ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
295
331
  # Checks connection, returns true if active. Note that ping actively
296
332
  # checks the connection, while #active? simply returns the last
297
333
  # known state.
298
- def ping
334
+ def ping #:nodoc:
299
335
  @connection.exec("select 1 from dual") { |r| nil }
300
336
  @active = true
301
337
  rescue
@@ -304,7 +340,7 @@ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
304
340
  end
305
341
 
306
342
  # Resets connection, by logging off and creating a new connection.
307
- def reset!
343
+ def reset! #:nodoc:
308
344
  logoff rescue nil
309
345
  begin
310
346
  @connection = @factory.new_connection @config
@@ -321,18 +357,18 @@ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
321
357
  # ORA-03113: end-of-file on communication channel
322
358
  # ORA-03114: not connected to ORACLE
323
359
  # ORA-03135: connection lost contact
324
- LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ]
360
+ LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ] #:nodoc:
325
361
 
326
362
  # Adds auto-recovery functionality.
327
363
  #
328
364
  # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
329
- def exec(sql, *bindvars, &block)
365
+ def exec(sql, *bindvars, &block) #:nodoc:
330
366
  should_retry = self.class.auto_retry? && autocommit?
331
367
 
332
368
  begin
333
369
  @connection.exec(sql, *bindvars, &block)
334
370
  rescue OCIException => e
335
- raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
371
+ raise unless e.is_a?(OCIError) && LOST_CONNECTION_ERROR_CODES.include?(e.code)
336
372
  @active = false
337
373
  raise unless should_retry
338
374
  should_retry = false
@@ -341,11 +377,12 @@ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
341
377
  end
342
378
  end
343
379
 
344
- # RSI: otherwise not working in Ruby 1.9.1
380
+ # otherwise not working in Ruby 1.9.1
345
381
  if RUBY_VERSION =~ /^1\.9/
346
- def describe(name)
382
+ def describe(name) #:nodoc:
347
383
  @connection.describe(name)
348
384
  end
349
385
  end
350
386
 
351
387
  end
388
+ #:startdoc: