activerecord-oracle_enhanced-adapter 7.2.1-java

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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1977 -0
  3. data/License.txt +20 -0
  4. data/README.md +863 -0
  5. data/VERSION +1 -0
  6. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +7 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +24 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +137 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +344 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +47 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +295 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +63 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +71 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +561 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +45 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +457 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +195 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +186 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +94 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +99 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +197 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +738 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +366 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +34 -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 +865 -0
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +19 -0
  30. data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
  31. data/lib/active_record/type/oracle_enhanced/integer.rb +14 -0
  32. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  34. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  35. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  36. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  37. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  38. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  39. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  40. data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
  41. data/lib/arel/visitors/oracle.rb +216 -0
  42. data/lib/arel/visitors/oracle12.rb +121 -0
  43. data/lib/arel/visitors/oracle_common.rb +51 -0
  44. data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +24 -0
  45. data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +36 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +574 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
  51. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
  52. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  53. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1289 -0
  54. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +474 -0
  55. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
  56. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
  57. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  58. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
  59. data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
  60. data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
  61. data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
  62. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
  63. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  64. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +99 -0
  65. data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
  66. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +56 -0
  67. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  68. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
  69. data/spec/active_record/oracle_enhanced/type/text_spec.rb +244 -0
  70. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +77 -0
  71. data/spec/spec_config.yaml.template +11 -0
  72. data/spec/spec_helper.rb +214 -0
  73. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  74. data/spec/support/alter_system_user_password.sql +2 -0
  75. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  76. metadata +177 -0
@@ -0,0 +1,561 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "java"
5
+ require "jruby"
6
+
7
+ # ojdbc7.jar or ojdbc6.jar file should be in application ./lib directory or in load path or in ENV['PATH']
8
+
9
+ java_version = java.lang.System.getProperty("java.version")
10
+ # Dropping Java SE 6(1.6) or older version without deprecation cycle.
11
+ # Rails 5.0 already requires CRuby 2.2.2 or higher and JRuby 9.0 supporging CRuby 2.2 requires Java SE 7.
12
+ if java_version < "1.7"
13
+ raise "ERROR: Java SE 6 or older version is not supported. Upgrade Java version to Java SE 7 or higher"
14
+ end
15
+
16
+ # Oracle 11g client ojdbc6.jar is also compatible with Java 1.7
17
+ # Oracle 12c Release 1 client provides ojdbc7.jar
18
+ # Oracle 12c Release 2 client provides ojdbc8.jar
19
+ # Oracle 21c provides ojdbc11.jar for Java 11 and above
20
+ ojdbc_jars = %w(ojdbc11.jar ojdbc8.jar ojdbc7.jar ojdbc6.jar)
21
+
22
+ if !ENV_JAVA["java.class.path"]&.match?(Regexp.new(ojdbc_jars.join("|")))
23
+ # On Unix environment variable should be PATH, on Windows it is sometimes Path
24
+ env_path = (ENV["PATH"] || ENV["Path"] || "").split(File::PATH_SEPARATOR)
25
+ # Look for JDBC driver at first in lib subdirectory (application specific JDBC file version)
26
+ # then in Ruby load path and finally in environment PATH
27
+ ["./lib"].concat($LOAD_PATH).concat(env_path).detect do |dir|
28
+ # check any compatible JDBC driver in the priority order
29
+ ojdbc_jars.any? do |ojdbc_jar|
30
+ if File.exist?(file_path = File.join(dir, ojdbc_jar))
31
+ require file_path
32
+ true
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ ORACLE_DRIVER = Java::oracle.jdbc.OracleDriver.new
39
+ java.sql.DriverManager.registerDriver ORACLE_DRIVER
40
+
41
+ # set tns_admin property from TNS_ADMIN environment variable
42
+ if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"]
43
+ java.lang.System.set_property("oracle.net.tns_admin", ENV["TNS_ADMIN"])
44
+ end
45
+
46
+ rescue LoadError, NameError => e
47
+ # JDBC driver is unavailable.
48
+ raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. Please install #{ojdbc_jars.join(' or ') } library.\n#{e.class}:#{e.message}"
49
+ end
50
+
51
+ module ActiveRecord
52
+ module ConnectionAdapters
53
+ # JDBC database interface for JRuby
54
+ module OracleEnhanced
55
+ class JDBCConnection < OracleEnhanced::Connection # :nodoc:
56
+ attr_accessor :active
57
+ alias :active? :active
58
+
59
+ attr_accessor :auto_retry
60
+ alias :auto_retry? :auto_retry
61
+ @auto_retry = false
62
+
63
+ def initialize(config)
64
+ @active = true
65
+ @config = config
66
+ new_connection(@config)
67
+ end
68
+
69
+ # modified method to support JNDI connections
70
+ def new_connection(config)
71
+ username = nil
72
+
73
+ if config[:jndi]
74
+ jndi = config[:jndi].to_s
75
+ ctx = javax.naming.InitialContext.new
76
+ ds = nil
77
+
78
+ # tomcat needs first lookup method, oc4j (and maybe other application servers) need second method
79
+ begin
80
+ env = ctx.lookup("java:/comp/env")
81
+ ds = env.lookup(jndi)
82
+ rescue
83
+ ds = ctx.lookup(jndi)
84
+ end
85
+
86
+ # check if datasource supports pooled connections, otherwise use default
87
+ if ds.respond_to?(:pooled_connection)
88
+ @raw_connection = ds.pooled_connection
89
+ else
90
+ @raw_connection = ds.connection
91
+ end
92
+
93
+ # get Oracle JDBC connection when using DBCP in Tomcat or jBoss
94
+ if @raw_connection.respond_to?(:getInnermostDelegate)
95
+ @pooled_connection = @raw_connection
96
+ @raw_connection = @raw_connection.innermost_delegate
97
+ elsif @raw_connection.respond_to?(:getUnderlyingConnection)
98
+ @pooled_connection = @raw_connection
99
+ @raw_connection = @raw_connection.underlying_connection
100
+ end
101
+
102
+ # Workaround FrozenError (can't modify frozen Hash):
103
+ config = config.dup
104
+ config[:driver] ||= @raw_connection.meta_data.connection.java_class.name
105
+ username = @raw_connection.meta_data.user_name
106
+ else
107
+ # to_s needed if username, password or database is specified as number in database.yml file
108
+ username = config[:username] && config[:username].to_s
109
+ password = config[:password] && config[:password].to_s
110
+ database = config[:database] && config[:database].to_s || "XE"
111
+ host, port = config[:host], config[:port]
112
+ privilege = config[:privilege] && config[:privilege].to_s
113
+
114
+ # connection using TNS alias, or connection-string from DATABASE_URL
115
+ using_tns_alias = !host && !config[:url] && ENV["TNS_ADMIN"]
116
+ if database && (using_tns_alias || host == "connection-string")
117
+ url = "jdbc:oracle:thin:@#{database}"
118
+ else
119
+ unless database.match?(/^(:|\/)/)
120
+ # assume database is a SID if no colon or slash are supplied (backward-compatibility)
121
+ database = "/#{database}"
122
+ end
123
+ url = config[:url] || "jdbc:oracle:thin:@//#{host || 'localhost'}:#{port || 1521}#{database}"
124
+ end
125
+
126
+ prefetch_rows = config[:prefetch_rows] || 100
127
+ # get session time_zone from configuration or from TZ environment variable
128
+ time_zone = config[:time_zone] || ENV["TZ"] || java.util.TimeZone.default.getID
129
+
130
+ properties = java.util.Properties.new
131
+ raise "username not set" unless username
132
+ raise "password not set" unless password
133
+ properties.put("user", username)
134
+ properties.put("password", password)
135
+ properties.put("defaultRowPrefetch", "#{prefetch_rows}") if prefetch_rows
136
+ properties.put("internal_logon", privilege) if privilege
137
+
138
+ if config[:jdbc_connect_properties] # arbitrary additional properties for JDBC connection
139
+ raise "jdbc_connect_properties should contain an associative array / hash" unless config[:jdbc_connect_properties].is_a? Hash
140
+ config[:jdbc_connect_properties].each do |key, value|
141
+ properties.put(key, value)
142
+ end
143
+ end
144
+
145
+ begin
146
+ @raw_connection = java.sql.DriverManager.getConnection(url, properties)
147
+ rescue
148
+ # bypass DriverManager to work in cases where ojdbc*.jar
149
+ # is added to the load path at runtime and not on the
150
+ # system classpath
151
+ @raw_connection = ORACLE_DRIVER.connect(url, properties)
152
+ end
153
+
154
+ # Set session time zone to current time zone
155
+ if ActiveRecord.default_timezone == :local
156
+ @raw_connection.setSessionTimeZone(time_zone)
157
+ elsif ActiveRecord.default_timezone == :utc
158
+ @raw_connection.setSessionTimeZone("UTC")
159
+ end
160
+
161
+ if config[:jdbc_statement_cache_size]
162
+ raise "Integer value expected for :jdbc_statement_cache_size" unless config[:jdbc_statement_cache_size].instance_of? Integer
163
+ @raw_connection.setImplicitCachingEnabled(true)
164
+ @raw_connection.setStatementCacheSize(config[:jdbc_statement_cache_size])
165
+ end
166
+
167
+ # Set default number of rows to prefetch
168
+ # @raw_connection.setDefaultRowPrefetch(prefetch_rows) if prefetch_rows
169
+ end
170
+
171
+ cursor_sharing = config[:cursor_sharing] || "force"
172
+ exec "alter session set cursor_sharing = #{cursor_sharing}" if cursor_sharing
173
+
174
+ # Initialize NLS parameters
175
+ OracleEnhancedAdapter::DEFAULT_NLS_PARAMETERS.each do |key, default_value|
176
+ value = config[key] || ENV[key.to_s.upcase] || default_value
177
+ if value
178
+ exec "alter session set #{key} = '#{value}'"
179
+ end
180
+ end
181
+
182
+ OracleEnhancedAdapter::FIXED_NLS_PARAMETERS.each do |key, value|
183
+ exec "alter session set #{key} = '#{value}'"
184
+ end
185
+
186
+ self.autocommit = true
187
+
188
+ schema = config[:schema] && config[:schema].to_s
189
+ if schema.blank?
190
+ # default schema owner
191
+ @owner = username.upcase unless username.nil?
192
+ else
193
+ exec "alter session set current_schema = #{schema}"
194
+ @owner = schema
195
+ end
196
+
197
+ @raw_connection
198
+ end
199
+
200
+ def logoff
201
+ @active = false
202
+ if defined?(@pooled_connection)
203
+ @pooled_connection.close
204
+ else
205
+ @raw_connection.close
206
+ end
207
+ true
208
+ rescue
209
+ false
210
+ end
211
+
212
+ def commit
213
+ @raw_connection.commit
214
+ end
215
+
216
+ def rollback
217
+ @raw_connection.rollback
218
+ end
219
+
220
+ def autocommit?
221
+ @raw_connection.getAutoCommit
222
+ end
223
+
224
+ def autocommit=(value)
225
+ @raw_connection.setAutoCommit(value)
226
+ end
227
+
228
+ # Checks connection, returns true if active. Note that ping actively
229
+ # checks the connection, while #active? simply returns the last
230
+ # known state.
231
+ def ping
232
+ exec_no_retry("select 1 from dual")
233
+ @active = true
234
+ rescue Java::JavaSql::SQLException => e
235
+ @active = false
236
+ raise OracleEnhanced::ConnectionException, e.message
237
+ end
238
+
239
+ def reset
240
+ # tentative
241
+ reset!
242
+ end
243
+
244
+ # Resets connection, by logging off and creating a new connection.
245
+ def reset!
246
+ logoff rescue nil
247
+ begin
248
+ new_connection(@config)
249
+ @active = true
250
+ rescue Java::JavaSql::SQLException => e
251
+ @active = false
252
+ raise OracleEnhanced::ConnectionException, e.message
253
+ end
254
+ end
255
+
256
+ # mark connection as dead if connection lost
257
+ def with_retry(allow_retry: false, &block)
258
+ should_retry = (allow_retry || auto_retry?) && autocommit?
259
+ begin
260
+ yield if block_given?
261
+ rescue Java::JavaSql::SQLException => e
262
+ raise unless /^(Closed Connection|Io exception:|No more data to read from socket|IO Error:)/.match?(e.message)
263
+ @active = false
264
+ raise unless should_retry
265
+ should_retry = false
266
+ reset! rescue nil
267
+ retry
268
+ end
269
+ end
270
+
271
+ def exec(sql, allow_retry: false)
272
+ with_retry(allow_retry: allow_retry) do
273
+ exec_no_retry(sql)
274
+ end
275
+ end
276
+
277
+ def exec_no_retry(sql)
278
+ case sql
279
+ when /\A\s*(UPDATE|INSERT|DELETE)/i
280
+ s = @raw_connection.prepareStatement(sql)
281
+ s.executeUpdate
282
+ # it is safer for CREATE and DROP statements not to use PreparedStatement
283
+ # as it does not allow creation of triggers with :NEW in their definition
284
+ when /\A\s*(CREATE|DROP)/i
285
+ s = @raw_connection.createStatement()
286
+ # this disables SQL92 syntax processing of {...} which can result in statement execution errors
287
+ # if sql contains {...} in strings or comments
288
+ s.setEscapeProcessing(false)
289
+ s.execute(sql)
290
+ true
291
+ else
292
+ s = @raw_connection.prepareStatement(sql)
293
+ s.execute
294
+ true
295
+ end
296
+ ensure
297
+ s.close rescue nil
298
+ end
299
+
300
+ def prepare(sql)
301
+ Cursor.new(self, @raw_connection.prepareStatement(sql))
302
+ end
303
+
304
+ def database_version
305
+ @database_version ||= (md = raw_connection.getMetaData) && [md.getDatabaseMajorVersion, md.getDatabaseMinorVersion]
306
+ end
307
+
308
+ class Cursor
309
+ def initialize(connection, raw_statement)
310
+ @raw_connection = connection
311
+ @raw_statement = raw_statement
312
+ end
313
+
314
+ def bind_params(*bind_vars)
315
+ index = 1
316
+ bind_vars.flatten.each do |var|
317
+ if Hash === var
318
+ var.each { |key, val| bind_param key, val }
319
+ else
320
+ bind_param index, var
321
+ index += 1
322
+ end
323
+ end
324
+ end
325
+
326
+ def bind_param(position, value)
327
+ case value
328
+ when Integer
329
+ @raw_statement.setLong(position, value)
330
+ when Float
331
+ @raw_statement.setFloat(position, value)
332
+ when BigDecimal
333
+ @raw_statement.setBigDecimal(position, value)
334
+ when Java::OracleSql::BLOB
335
+ @raw_statement.setBlob(position, value)
336
+ when Java::OracleSql::CLOB
337
+ @raw_statement.setClob(position, value)
338
+ when Java::OracleSql::NCLOB
339
+ @raw_statement.setClob(position, value)
340
+ when Type::OracleEnhanced::Raw
341
+ @raw_statement.setString(position, OracleEnhanced::Quoting.encode_raw(value))
342
+ when Type::OracleEnhanced::CharacterString::Data
343
+ @raw_statement.setFixedCHAR(position, value.to_s)
344
+ when String
345
+ @raw_statement.setString(position, value)
346
+ when Java::OracleSql::DATE
347
+ @raw_statement.setDATE(position, value)
348
+ when Java::JavaSql::Timestamp
349
+ @raw_statement.setTimestamp(position, value)
350
+ when Time
351
+ new_value = Java::java.sql.Timestamp.new(value.year - 1900, value.month - 1, value.day, value.hour, value.min, value.sec, value.usec * 1000)
352
+ @raw_statement.setTimestamp(position, new_value)
353
+ when NilClass
354
+ # TODO: currently nil is always bound as NULL with VARCHAR type.
355
+ # When nils will actually be used by ActiveRecord as bound parameters
356
+ # then need to pass actual column type.
357
+ @raw_statement.setNull(position, java.sql.Types::VARCHAR)
358
+ else
359
+ raise ArgumentError, "Don't know how to bind variable with type #{value.class}"
360
+ end
361
+ end
362
+
363
+ def bind_returning_param(position, bind_type)
364
+ @returning_positions ||= []
365
+ @returning_positions << position
366
+ if bind_type == Integer
367
+ @raw_statement.registerReturnParameter(position, java.sql.Types::BIGINT)
368
+ end
369
+ end
370
+
371
+ def exec
372
+ @raw_result_set = @raw_statement.executeQuery
373
+ true
374
+ end
375
+
376
+ def exec_update
377
+ @raw_statement.executeUpdate
378
+ end
379
+
380
+ def metadata
381
+ @metadata ||= @raw_result_set.getMetaData
382
+ end
383
+
384
+ def column_types
385
+ @column_types ||= (1..metadata.getColumnCount).map { |i| metadata.getColumnTypeName(i).to_sym }
386
+ end
387
+
388
+ def column_names
389
+ @column_names ||= (1..metadata.getColumnCount).map { |i| metadata.getColumnName(i) }
390
+ end
391
+ alias :get_col_names :column_names
392
+
393
+ def fetch(options = {})
394
+ if @raw_result_set.next
395
+ get_lob_value = options[:get_lob_value]
396
+ row_values = []
397
+ column_types.each_with_index do |column_type, i|
398
+ row_values <<
399
+ @raw_connection.get_ruby_value_from_result_set(@raw_result_set, i + 1, column_type, get_lob_value)
400
+ end
401
+ row_values
402
+ else
403
+ @raw_result_set.close
404
+ nil
405
+ end
406
+ end
407
+
408
+ def get_returning_param(position, type)
409
+ rs_position = @returning_positions.index(position) + 1
410
+ rs = @raw_statement.getReturnResultSet
411
+ if rs.next
412
+ # Assuming that primary key will not be larger as long max value
413
+ returning_id = rs.getLong(rs_position)
414
+ rs.wasNull ? nil : returning_id
415
+ else
416
+ nil
417
+ end
418
+ end
419
+
420
+ def close
421
+ @raw_statement.close
422
+ end
423
+ end
424
+
425
+ def select(sql, name = nil, return_column_names = false)
426
+ with_retry do
427
+ select_no_retry(sql, name, return_column_names)
428
+ end
429
+ end
430
+
431
+ def select_no_retry(sql, name = nil, return_column_names = false)
432
+ stmt = @raw_connection.prepareStatement(sql)
433
+ rset = stmt.executeQuery
434
+
435
+ # Reuse the same hash for all rows
436
+ column_hash = {}
437
+
438
+ metadata = rset.getMetaData
439
+ column_count = metadata.getColumnCount
440
+
441
+ cols_types_index = (1..column_count).map do |i|
442
+ col_name = _oracle_downcase(metadata.getColumnName(i))
443
+ next if col_name == "raw_rnum_"
444
+ column_hash[col_name] = nil
445
+ [col_name, metadata.getColumnTypeName(i).to_sym, i]
446
+ end
447
+ cols_types_index.delete(nil)
448
+
449
+ rows = []
450
+ get_lob_value = !(name == "Writable Large Object")
451
+
452
+ while rset.next
453
+ hash = column_hash.dup
454
+ cols_types_index.each do |col, column_type, i|
455
+ hash[col] = get_ruby_value_from_result_set(rset, i, column_type, get_lob_value)
456
+ end
457
+ rows << hash
458
+ end
459
+
460
+ return_column_names ? [rows, cols_types_index.map(&:first)] : rows
461
+ ensure
462
+ rset.close rescue nil
463
+ stmt.close rescue nil
464
+ end
465
+
466
+ def write_lob(lob, value, is_binary = false)
467
+ if is_binary
468
+ lob.setBytes(1, value.to_java_bytes)
469
+ else
470
+ lob.setString(1, value)
471
+ end
472
+ end
473
+
474
+ # To allow private method called from `JDBCConnection`
475
+ def describe(name)
476
+ super
477
+ end
478
+
479
+ # Return java.sql.SQLException error code
480
+ def error_code(exception)
481
+ case exception
482
+ when Java::JavaSql::SQLException
483
+ exception.getErrorCode
484
+ else
485
+ nil
486
+ end
487
+ end
488
+
489
+ def get_ruby_value_from_result_set(rset, i, type_name, get_lob_value = true)
490
+ case type_name
491
+ when :NUMBER
492
+ d = rset.getNUMBER(i)
493
+ if d.nil?
494
+ nil
495
+ elsif d.isInt
496
+ Integer(d.stringValue)
497
+ else
498
+ BigDecimal(d.stringValue)
499
+ end
500
+ when :BINARY_FLOAT
501
+ rset.getFloat(i)
502
+ when :VARCHAR2, :LONG, :NVARCHAR2
503
+ rset.getString(i)
504
+ when :CHAR, :NCHAR
505
+ char_str = rset.getString(i)
506
+ if !char_str.nil?
507
+ char_str.rstrip
508
+ end
509
+ when :DATE
510
+ if dt = rset.getDATE(i)
511
+ d = dt.dateValue
512
+ t = dt.timeValue
513
+ Time.send(ActiveRecord.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
514
+ else
515
+ nil
516
+ end
517
+ when :TIMESTAMP, :TIMESTAMPTZ, :TIMESTAMPLTZ, :"TIMESTAMP WITH TIME ZONE", :"TIMESTAMP WITH LOCAL TIME ZONE"
518
+ ts = rset.getTimestamp(i)
519
+ ts && Time.send(ActiveRecord.default_timezone, ts.year + 1900, ts.month + 1, ts.date, ts.hours, ts.minutes, ts.seconds,
520
+ ts.nanos / 1000)
521
+ when :CLOB
522
+ get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
523
+ when :NCLOB
524
+ get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
525
+ when :BLOB
526
+ get_lob_value ? lob_to_ruby_value(rset.getBlob(i)) : rset.getBlob(i)
527
+ when :RAW
528
+ raw_value = rset.getRAW(i)
529
+ raw_value && raw_value.getBytes.to_a.pack("C*")
530
+ else
531
+ nil
532
+ end
533
+ end
534
+
535
+ private
536
+ def lob_to_ruby_value(val)
537
+ case val
538
+ when ::Java::OracleSql::CLOB
539
+ if val.isEmptyLob
540
+ nil
541
+ else
542
+ val.getSubString(1, val.length)
543
+ end
544
+ when ::Java::OracleSql::NCLOB
545
+ if val.isEmptyLob
546
+ nil
547
+ else
548
+ val.getSubString(1, val.length)
549
+ end
550
+ when ::Java::OracleSql::BLOB
551
+ if val.isEmptyLob
552
+ nil
553
+ else
554
+ String.from_java_bytes(val.getBytes(1, val.length))
555
+ end
556
+ end
557
+ end
558
+ end
559
+ end
560
+ end
561
+ end
@@ -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(@raw_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(@raw_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(@raw_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,45 @@
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.end_with?("LOB")
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+ def enhanced_write_lobs
28
+ if self.class.connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
29
+ !(self.class.custom_create_method || self.class.custom_update_method)
30
+ self.class.connection.write_lobs(self.class.table_name, self.class, attributes, @changed_lob_columns)
31
+ end
32
+ end
33
+ def record_changed_lobs
34
+ @changed_lob_columns = self.class.lob_columns.select do |col|
35
+ self.will_save_change_to_attribute?(col.name) && !self.class.readonly_attributes.to_a.include?(col.name)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ ActiveSupport.on_load(:active_record) do
44
+ ActiveRecord::Base.send(:include, ActiveRecord::ConnectionAdapters::OracleEnhanced::Lob)
45
+ end