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,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,64 @@
1
+ require "bigdecimal"
2
+ unless BigDecimal.instance_methods.include?("to_d")
3
+ BigDecimal.class_eval do
4
+ def to_d
5
+ self
6
+ end
7
+ end
8
+ end
9
+
10
+ unless Bignum.instance_methods.include?("to_d")
11
+ Bignum.class_eval do
12
+ def to_d
13
+ BigDecimal.new(self.to_s)
14
+ end
15
+ end
16
+ end
17
+
18
+ unless Fixnum.instance_methods.include?("to_d")
19
+ Fixnum.class_eval do
20
+ def to_d
21
+ BigDecimal.new(self.to_s)
22
+ end
23
+ end
24
+ end
25
+
26
+ # Add Unicode aware String#upcase and String#downcase methods when mb_chars method is called
27
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && RUBY_VERSION >= '1.9'
28
+ begin
29
+ gem "unicode_utils", ">=1.0.0"
30
+ require "unicode_utils/upcase"
31
+ require "unicode_utils/downcase"
32
+
33
+ module ActiveRecord
34
+ module ConnectionAdapters
35
+ module OracleEnhancedUnicodeString
36
+ def upcase
37
+ UnicodeUtils.upcase(self)
38
+ end
39
+
40
+ def downcase
41
+ UnicodeUtils.downcase(self)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ class String
48
+ def mb_chars
49
+ self.extend(ActiveRecord::ConnectionAdapters::OracleEnhancedUnicodeString)
50
+ self
51
+ end
52
+ end
53
+
54
+ rescue LoadError
55
+ warning_message = "WARNING: Please install unicode_utils gem to support Unicode aware upcase and downcase for String#mb_chars"
56
+ if defined?(RAILS_DEFAULT_LOGGER)
57
+ RAILS_DEFAULT_LOGGER.warn warning_message
58
+ else
59
+ STDERR.puts warning_message
60
+ end
61
+ end
62
+
63
+
64
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveRecord #:nodoc:
2
+ module ConnectionAdapters #:nodoc:
3
+ module OracleEnhancedCpk #:nodoc:
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
7
+ def supports_count_distinct? #:nodoc:
8
+ @supports_count_distinct ||= ! defined?(CompositePrimaryKeys)
9
+ end
10
+
11
+ def concat(*columns)
12
+ "(#{columns.join('||')})"
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+
19
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
20
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedCpk
21
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveRecord #:nodoc:
2
+ module ConnectionAdapters #:nodoc:
3
+ module OracleEnhancedDirty #:nodoc:
4
+
5
+ module InstanceMethods
6
+ private
7
+
8
+ def field_changed?(attr, old, value)
9
+ if column = column_for_attribute(attr)
10
+ # RSI: added also :decimal type
11
+ if (column.type == :integer || column.type == :decimal) && column.null && (old.nil? || old == 0) && value.blank?
12
+ # For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
13
+ # Hence we don't record it as a change if the value changes from nil to ''.
14
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
15
+ # be typecast back to 0 (''.to_i => 0)
16
+ value = nil
17
+ # RSI: Oracle stores empty string '' or empty text (CLOB) as NULL
18
+ # therefore need to convert empty string value to nil if old value is nil
19
+ elsif (column.type == :string || column.type == :text) && column.null && old.nil?
20
+ value = nil if value == ''
21
+ else
22
+ value = column.type_cast(value)
23
+ end
24
+ end
25
+
26
+ old != value
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+
35
+ if ActiveRecord::Base.instance_methods.include?('changed?')
36
+ ActiveRecord::Base.class_eval do
37
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedDirty::InstanceMethods
38
+ end
39
+ end
@@ -0,0 +1,341 @@
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
+ # Set default number of rows to prefetch
76
+ # @raw_connection.setDefaultRowPrefetch(prefetch_rows) if prefetch_rows
77
+
78
+ # default schema owner
79
+ @owner = username.upcase
80
+
81
+ @raw_connection
82
+ end
83
+
84
+
85
+ def logoff
86
+ @active = false
87
+ @raw_connection.close
88
+ true
89
+ rescue
90
+ false
91
+ end
92
+
93
+ def commit
94
+ @raw_connection.commit
95
+ end
96
+
97
+ def rollback
98
+ @raw_connection.rollback
99
+ end
100
+
101
+ def autocommit?
102
+ @raw_connection.getAutoCommit
103
+ end
104
+
105
+ def autocommit=(value)
106
+ @raw_connection.setAutoCommit(value)
107
+ end
108
+
109
+ # Checks connection, returns true if active. Note that ping actively
110
+ # checks the connection, while #active? simply returns the last
111
+ # known state.
112
+ def ping
113
+ exec_no_retry("select 1 from dual")
114
+ @active = true
115
+ rescue NativeException => e
116
+ @active = false
117
+ if e.message =~ /^java\.sql\.SQLException/
118
+ raise OracleEnhancedConnectionException, e.message
119
+ else
120
+ raise
121
+ end
122
+ end
123
+
124
+ # Resets connection, by logging off and creating a new connection.
125
+ def reset!
126
+ logoff rescue nil
127
+ begin
128
+ new_connection(@config)
129
+ @active = true
130
+ rescue NativeException => e
131
+ @active = false
132
+ if e.message =~ /^java\.sql\.SQLException/
133
+ raise OracleEnhancedConnectionException, e.message
134
+ else
135
+ raise
136
+ end
137
+ end
138
+ end
139
+
140
+ # mark connection as dead if connection lost
141
+ def with_retry(&block)
142
+ should_retry = auto_retry? && autocommit?
143
+ begin
144
+ yield if block_given?
145
+ rescue NativeException => e
146
+ raise unless e.message =~ /^java\.sql\.SQLException: (Closed Connection|Io exception:|No more data to read from socket)/
147
+ @active = false
148
+ raise unless should_retry
149
+ should_retry = false
150
+ reset! rescue nil
151
+ retry
152
+ end
153
+ end
154
+
155
+ def exec(sql)
156
+ with_retry do
157
+ exec_no_retry(sql)
158
+ end
159
+ end
160
+
161
+ def exec_no_retry(sql)
162
+ cs = @raw_connection.prepareCall(sql)
163
+ case sql
164
+ when /\A\s*UPDATE/i, /\A\s*INSERT/i, /\A\s*DELETE/i
165
+ cs.executeUpdate
166
+ else
167
+ cs.execute
168
+ true
169
+ end
170
+ ensure
171
+ cs.close rescue nil
172
+ end
173
+
174
+ def select(sql, name = nil, return_column_names = false)
175
+ with_retry do
176
+ select_no_retry(sql, name, return_column_names)
177
+ end
178
+ end
179
+
180
+ def select_no_retry(sql, name = nil, return_column_names = false)
181
+ stmt = @raw_connection.prepareStatement(sql)
182
+ rset = stmt.executeQuery
183
+
184
+ # Reuse the same hash for all rows
185
+ column_hash = {}
186
+
187
+ metadata = rset.getMetaData
188
+ column_count = metadata.getColumnCount
189
+
190
+ cols_types_index = (1..column_count).map do |i|
191
+ col_name = oracle_downcase(metadata.getColumnName(i))
192
+ next if col_name == 'raw_rnum_'
193
+ column_hash[col_name] = nil
194
+ [col_name, metadata.getColumnTypeName(i).to_sym, i]
195
+ end
196
+ cols_types_index.delete(nil)
197
+
198
+ rows = []
199
+ get_lob_value = !(name == 'Writable Large Object')
200
+
201
+ while rset.next
202
+ hash = column_hash.dup
203
+ cols_types_index.each do |col, column_type, i|
204
+ hash[col] = get_ruby_value_from_result_set(rset, i, column_type, get_lob_value)
205
+ end
206
+ rows << hash
207
+ end
208
+
209
+ return_column_names ? [rows, cols_types_index.map(&:first)] : rows
210
+ ensure
211
+ rset.close rescue nil
212
+ stmt.close rescue nil
213
+ end
214
+
215
+ def write_lob(lob, value, is_binary = false)
216
+ if is_binary
217
+ lob.setBytes(1, value.to_java_bytes)
218
+ else
219
+ lob.setString(1,value)
220
+ end
221
+ end
222
+
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
261
+ end
262
+
263
+
264
+ private
265
+
266
+ # def prepare_statement(sql)
267
+ # @raw_connection.prepareStatement(sql)
268
+ # end
269
+
270
+ # def prepare_call(sql, *bindvars)
271
+ # @raw_connection.prepareCall(sql)
272
+ # end
273
+
274
+ def get_ruby_value_from_result_set(rset, i, type_name, get_lob_value = true)
275
+ case type_name
276
+ when :NUMBER
277
+ # d = rset.getBigDecimal(i)
278
+ # if d.nil?
279
+ # nil
280
+ # elsif d.scale == 0
281
+ # d.toBigInteger+0
282
+ # else
283
+ # # Is there better way how to convert Java BigDecimal to Ruby BigDecimal?
284
+ # d.toString.to_d
285
+ # end
286
+ d = rset.getNUMBER(i)
287
+ if d.nil?
288
+ nil
289
+ elsif d.isInt
290
+ Integer(d.stringValue)
291
+ else
292
+ BigDecimal.new(d.stringValue)
293
+ end
294
+ when :VARCHAR2, :CHAR, :LONG
295
+ rset.getString(i)
296
+ when :DATE
297
+ if dt = rset.getDATE(i)
298
+ d = dt.dateValue
299
+ t = dt.timeValue
300
+ if OracleEnhancedAdapter.emulate_dates && t.hours == 0 && t.minutes == 0 && t.seconds == 0
301
+ Date.new(d.year + 1900, d.month + 1, d.date)
302
+ else
303
+ Time.send(Base.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
304
+ end
305
+ else
306
+ nil
307
+ end
308
+ when :TIMESTAMP, :TIMESTAMPTZ, :TIMESTAMPLTZ
309
+ ts = rset.getTimestamp(i)
310
+ ts && Time.send(Base.default_timezone, ts.year + 1900, ts.month + 1, ts.date, ts.hours, ts.minutes, ts.seconds,
311
+ ts.nanos / 1000)
312
+ when :CLOB
313
+ get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
314
+ when :BLOB
315
+ get_lob_value ? lob_to_ruby_value(rset.getBlob(i)) : rset.getBlob(i)
316
+ else
317
+ nil
318
+ end
319
+ end
320
+
321
+ def lob_to_ruby_value(val)
322
+ case val
323
+ when ::Java::OracleSql::CLOB
324
+ if val.isEmptyLob
325
+ nil
326
+ else
327
+ val.getSubString(1, val.length)
328
+ end
329
+ when ::Java::OracleSql::BLOB
330
+ if val.isEmptyLob
331
+ nil
332
+ else
333
+ String.from_java_bytes(val.getBytes(1, val.length))
334
+ end
335
+ end
336
+ end
337
+
338
+ end
339
+
340
+ end
341
+ end