activerecord-oracle_enhanced-adapter 8.1.0-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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1971 -0
  3. data/License.txt +20 -0
  4. data/README.md +947 -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 +359 -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 +325 -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 +629 -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 +57 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +465 -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 +95 -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 +739 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +394 -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 +886 -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 +40 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/composite_spec.rb +84 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +589 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced/dbconsole_spec.rb +63 -0
  51. data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
  52. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
  53. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
  54. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  55. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1318 -0
  56. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +485 -0
  57. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
  58. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
  59. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  60. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
  61. data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
  62. data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
  63. data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
  64. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
  65. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  66. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +101 -0
  67. data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
  68. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
  69. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  70. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
  71. data/spec/active_record/oracle_enhanced/type/text_spec.rb +295 -0
  72. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +107 -0
  73. data/spec/spec_config.yaml.template +11 -0
  74. data/spec/spec_helper.rb +225 -0
  75. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  76. data/spec/support/alter_system_user_password.sql +2 -0
  77. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  78. metadata +181 -0
@@ -0,0 +1,629 @@
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
+ # Oracle 23c provides ojdbc17.jar for Java 17 and above
21
+ ojdbc_jars = %w(ojdbc17.jar ojdbc11.jar ojdbc8.jar ojdbc7.jar ojdbc6.jar)
22
+
23
+ if !ENV_JAVA["java.class.path"]&.match?(Regexp.new(ojdbc_jars.join("|")))
24
+ # On Unix environment variable should be PATH, on Windows it is sometimes Path
25
+ env_path = (ENV["PATH"] || ENV["Path"] || "").split(File::PATH_SEPARATOR)
26
+ # Look for JDBC driver at first in lib subdirectory (application specific JDBC file version)
27
+ # then in Ruby load path and finally in environment PATH
28
+ ["./lib"].concat($LOAD_PATH).concat(env_path).detect do |dir|
29
+ # check any compatible JDBC driver in the priority order
30
+ ojdbc_jars.any? do |ojdbc_jar|
31
+ if File.exist?(file_path = File.join(dir, ojdbc_jar))
32
+ require file_path
33
+ true
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ ORACLE_DRIVER = Java::oracle.jdbc.OracleDriver.new
40
+ java.sql.DriverManager.registerDriver ORACLE_DRIVER
41
+
42
+ # set tns_admin property from TNS_ADMIN environment variable
43
+ if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"]
44
+ java.lang.System.set_property("oracle.net.tns_admin", ENV["TNS_ADMIN"])
45
+ end
46
+
47
+ rescue LoadError, NameError => e
48
+ # JDBC driver is unavailable.
49
+ 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}"
50
+ end
51
+
52
+ module ActiveRecord
53
+ module ConnectionAdapters
54
+ # JDBC database interface for JRuby
55
+ module OracleEnhanced
56
+ class JDBCConnection < OracleEnhanced::Connection # :nodoc:
57
+ attr_accessor :active
58
+ alias :active? :active
59
+
60
+ attr_accessor :auto_retry
61
+ alias :auto_retry? :auto_retry
62
+ @auto_retry = false
63
+
64
+ attr_reader :session_time_zone
65
+
66
+ def initialize(config)
67
+ @active = true
68
+ @config = config
69
+ new_connection(@config)
70
+ end
71
+
72
+ # modified method to support JNDI connections
73
+ def new_connection(config)
74
+ username = nil
75
+
76
+ if config[:jndi]
77
+ jndi = config[:jndi].to_s
78
+ ctx = javax.naming.InitialContext.new
79
+ ds = nil
80
+
81
+ # tomcat needs first lookup method, oc4j (and maybe other application servers) need second method
82
+ begin
83
+ env = ctx.lookup("java:/comp/env")
84
+ ds = env.lookup(jndi)
85
+ rescue
86
+ ds = ctx.lookup(jndi)
87
+ end
88
+
89
+ # check if datasource supports pooled connections, otherwise use default
90
+ if ds.respond_to?(:pooled_connection)
91
+ @raw_connection = ds.pooled_connection
92
+ else
93
+ @raw_connection = ds.connection
94
+ end
95
+
96
+ # get Oracle JDBC connection when using DBCP in Tomcat or jBoss
97
+ if @raw_connection.respond_to?(:getInnermostDelegate)
98
+ @pooled_connection = @raw_connection
99
+ @raw_connection = @raw_connection.innermost_delegate
100
+ elsif @raw_connection.respond_to?(:getUnderlyingConnection)
101
+ @pooled_connection = @raw_connection
102
+ @raw_connection = @raw_connection.underlying_connection
103
+ end
104
+
105
+ # Workaround FrozenError (can't modify frozen Hash):
106
+ config = config.dup
107
+ config[:driver] ||= @raw_connection.meta_data.connection.java_class.name
108
+ username = @raw_connection.meta_data.user_name
109
+ else
110
+ # to_s needed if username, password or database is specified as number in database.yml file
111
+ username = config[:username] && config[:username].to_s
112
+ password = config[:password] && config[:password].to_s
113
+ database = config[:database] && config[:database].to_s || "XE"
114
+ host, port = config[:host], config[:port]
115
+ privilege = config[:privilege] && config[:privilege].to_s
116
+
117
+ # connection using TNS alias, or connection-string from DATABASE_URL
118
+ using_tns_alias = !host && !config[:url] && ENV["TNS_ADMIN"]
119
+ if database && (using_tns_alias || host == "connection-string")
120
+ url = "jdbc:oracle:thin:@#{database}"
121
+ else
122
+ unless database.match?(/^(:|\/)/)
123
+ # assume database is a SID if no colon or slash are supplied (backward-compatibility)
124
+ database = "/#{database}"
125
+ end
126
+ url = config[:url] || "jdbc:oracle:thin:@//#{host || 'localhost'}:#{port || 1521}#{database}"
127
+ end
128
+
129
+ prefetch_rows = config[:prefetch_rows] || 100
130
+ # get session time_zone from configuration or from TZ environment variable
131
+ time_zone = config[:time_zone] || ENV["TZ"] || java.util.TimeZone.default.getID
132
+
133
+ properties = java.util.Properties.new
134
+ raise "username not set" unless username
135
+ raise "password not set" unless password
136
+ properties.put("user", username)
137
+ properties.put("password", password)
138
+ properties.put("defaultRowPrefetch", "#{prefetch_rows}") if prefetch_rows
139
+ properties.put("internal_logon", privilege) if privilege
140
+
141
+ if config[:jdbc_connect_properties] # arbitrary additional properties for JDBC connection
142
+ raise "jdbc_connect_properties should contain an associative array / hash" unless config[:jdbc_connect_properties].is_a? Hash
143
+ config[:jdbc_connect_properties].each do |key, value|
144
+ properties.put(key, value)
145
+ end
146
+ end
147
+
148
+ begin
149
+ @raw_connection = java.sql.DriverManager.getConnection(url, properties)
150
+ rescue
151
+ # bypass DriverManager to work in cases where ojdbc*.jar
152
+ # is added to the load path at runtime and not on the
153
+ # system classpath
154
+ @raw_connection = ORACLE_DRIVER.connect(url, properties)
155
+ end
156
+
157
+ # Set session time zone to current time zone
158
+ if ActiveRecord.default_timezone == :local
159
+ @raw_connection.setSessionTimeZone(time_zone)
160
+ @session_time_zone = time_zone
161
+ elsif ActiveRecord.default_timezone == :utc
162
+ @raw_connection.setSessionTimeZone("UTC")
163
+ @session_time_zone = "UTC"
164
+ end
165
+
166
+ if config[:jdbc_statement_cache_size]
167
+ raise "Integer value expected for :jdbc_statement_cache_size" unless config[:jdbc_statement_cache_size].instance_of? Integer
168
+ @raw_connection.setImplicitCachingEnabled(true)
169
+ @raw_connection.setStatementCacheSize(config[:jdbc_statement_cache_size])
170
+ end
171
+
172
+ # Set default number of rows to prefetch
173
+ # @raw_connection.setDefaultRowPrefetch(prefetch_rows) if prefetch_rows
174
+ end
175
+
176
+ cursor_sharing = config[:cursor_sharing] || "force"
177
+ exec "alter session set cursor_sharing = #{cursor_sharing}" if cursor_sharing
178
+
179
+ # Initialize NLS parameters
180
+ OracleEnhancedAdapter::DEFAULT_NLS_PARAMETERS.each do |key, default_value|
181
+ value = config[key] || ENV[key.to_s.upcase] || default_value
182
+ if value
183
+ exec "alter session set #{key} = '#{value}'"
184
+ end
185
+ end
186
+
187
+ OracleEnhancedAdapter::FIXED_NLS_PARAMETERS.each do |key, value|
188
+ exec "alter session set #{key} = '#{value}'"
189
+ end
190
+
191
+ self.autocommit = true
192
+
193
+ schema = config[:schema] && config[:schema].to_s
194
+ if schema.blank?
195
+ # default schema owner
196
+ @owner = username.upcase unless username.nil?
197
+ else
198
+ exec "alter session set current_schema = #{schema}"
199
+ @owner = schema
200
+ end
201
+
202
+ @raw_connection
203
+ end
204
+
205
+ def logoff
206
+ @active = false
207
+ if defined?(@pooled_connection)
208
+ @pooled_connection.close
209
+ else
210
+ @raw_connection.close
211
+ end
212
+ true
213
+ rescue
214
+ false
215
+ end
216
+
217
+ def commit
218
+ @raw_connection.commit
219
+ end
220
+
221
+ def rollback
222
+ @raw_connection.rollback
223
+ end
224
+
225
+ def autocommit?
226
+ @raw_connection.getAutoCommit
227
+ end
228
+
229
+ def autocommit=(value)
230
+ @raw_connection.setAutoCommit(value)
231
+ end
232
+
233
+ # Checks connection, returns true if active. Note that ping actively
234
+ # checks the connection, while #active? simply returns the last
235
+ # known state.
236
+ def ping
237
+ exec_no_retry("select 1 from dual")
238
+ @active = true
239
+ rescue Java::JavaSql::SQLException => e
240
+ @active = false
241
+ raise OracleEnhanced::ConnectionException, e.message
242
+ end
243
+
244
+ # Resets connection, by logging off and creating a new connection.
245
+ def reset
246
+ reset!
247
+ end
248
+
249
+ def reset!
250
+ logoff rescue nil
251
+ begin
252
+ new_connection(@config)
253
+ @active = true
254
+ rescue Java::JavaSql::SQLException => e
255
+ @active = false
256
+ raise OracleEnhanced::ConnectionException, e.message
257
+ end
258
+ end
259
+
260
+ # mark connection as dead if connection lost
261
+ def with_retry(allow_retry: false, &block)
262
+ should_retry = (allow_retry || auto_retry?) && autocommit?
263
+ begin
264
+ yield if block_given?
265
+ rescue Java::JavaSql::SQLException => e
266
+ raise unless /^(Closed Connection|Io exception:|No more data to read from socket|IO Error:|ORA-03113:|ORA-03114:|ORA-17008:)/.match?(e.message)
267
+ @active = false
268
+ raise unless should_retry
269
+ should_retry = false
270
+ reset! rescue nil
271
+ retry
272
+ end
273
+ end
274
+
275
+ def exec(sql, *bindvars, allow_retry: false)
276
+ # The signature mirrors the OCI implementation for polymorphic
277
+ # callers, but the JDBC path here has no bindvar handling. Fail
278
+ # loudly rather than silently dropping values on the floor.
279
+ raise ArgumentError, "JDBC exec does not support bindvars" unless bindvars.empty?
280
+ with_retry(allow_retry: allow_retry) do
281
+ exec_no_retry(sql)
282
+ end
283
+ end
284
+
285
+ def exec_no_retry(sql)
286
+ case sql
287
+ when /\A\s*(UPDATE|INSERT|DELETE)/i
288
+ s = @raw_connection.prepareStatement(sql)
289
+ s.executeUpdate
290
+ # it is safer for CREATE and DROP statements not to use PreparedStatement
291
+ # as it does not allow creation of triggers with :NEW in their definition
292
+ when /\A\s*(CREATE|DROP)/i
293
+ s = @raw_connection.createStatement()
294
+ # this disables SQL92 syntax processing of {...} which can result in statement execution errors
295
+ # if sql contains {...} in strings or comments
296
+ s.setEscapeProcessing(false)
297
+ s.execute(sql)
298
+ true
299
+ else
300
+ s = @raw_connection.prepareStatement(sql)
301
+ s.execute
302
+ true
303
+ end
304
+ ensure
305
+ s.close rescue nil
306
+ end
307
+
308
+ def prepare(sql)
309
+ # Use a plain Statement for DDL and PL/SQL blocks: PreparedStatement
310
+ # interprets colons as bind-parameter markers which breaks trigger
311
+ # definitions (:NEW/:OLD) and PL/SQL with named parameters.
312
+ if /\A\s*(CREATE|DROP|BEGIN|DECLARE)/i.match?(sql)
313
+ s = @raw_connection.createStatement()
314
+ s.setEscapeProcessing(false)
315
+ Cursor.new(self, s, sql)
316
+ else
317
+ Cursor.new(self, @raw_connection.prepareStatement(sql))
318
+ end
319
+ end
320
+
321
+ def database_version
322
+ @database_version ||= (md = raw_connection.getMetaData) && [md.getDatabaseMajorVersion, md.getDatabaseMinorVersion]
323
+ end
324
+
325
+ class Cursor
326
+ def initialize(connection, raw_statement, exec_sql = nil)
327
+ @raw_connection = connection
328
+ @raw_statement = raw_statement
329
+ @exec_sql = exec_sql # non-nil for DDL/PL/SQL using createStatement
330
+ end
331
+
332
+ def bind_params(*bind_vars)
333
+ index = 1
334
+ bind_vars.flatten.each do |var|
335
+ if Hash === var
336
+ var.each { |key, val| bind_param key, val }
337
+ else
338
+ bind_param index, var
339
+ index += 1
340
+ end
341
+ end
342
+ end
343
+
344
+ def bind_param(position, value)
345
+ case value
346
+ when Integer
347
+ @raw_statement.setLong(position, value)
348
+ when Float
349
+ @raw_statement.setFloat(position, value)
350
+ when BigDecimal
351
+ @raw_statement.setBigDecimal(position, value)
352
+ when Java::OracleSql::BLOB
353
+ @raw_statement.setBlob(position, value)
354
+ when Java::OracleSql::CLOB
355
+ @raw_statement.setClob(position, value)
356
+ when Java::OracleSql::NCLOB
357
+ @raw_statement.setClob(position, value)
358
+ when Type::OracleEnhanced::Raw
359
+ @raw_statement.setString(position, OracleEnhanced::Quoting.encode_raw(value))
360
+ when Type::OracleEnhanced::CharacterString::Data
361
+ @raw_statement.setFixedCHAR(position, value.to_s)
362
+ when String
363
+ @raw_statement.setString(position, value)
364
+ when Java::OracleSql::DATE
365
+ @raw_statement.setDATE(position, value)
366
+ when Java::JavaSql::Timestamp
367
+ @raw_statement.setTimestamp(position, value)
368
+ when Time
369
+ new_value = java.sql.Timestamp.new(value.to_i * 1000)
370
+ new_value.setNanos(value.nsec)
371
+ # Oracle JDBC getTimestamp uses the session timezone, but setTimestamp
372
+ # (without Calendar) uses the JVM default timezone. Pass a Calendar
373
+ # matching the session timezone so both sides use the same reference.
374
+ tz_id = @raw_connection.session_time_zone || java.util.TimeZone.default.getID
375
+ cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone(tz_id))
376
+ @raw_statement.setTimestamp(position, new_value, cal)
377
+ when NilClass
378
+ # TODO: currently nil is always bound as NULL with VARCHAR type.
379
+ # When nils will actually be used by ActiveRecord as bound parameters
380
+ # then need to pass actual column type.
381
+ @raw_statement.setNull(position, java.sql.Types::VARCHAR)
382
+ else
383
+ raise ArgumentError, "Don't know how to bind variable with type #{value.class}"
384
+ end
385
+ end
386
+
387
+ def bind_returning_param(position, bind_type)
388
+ @returning_positions ||= []
389
+ @returning_positions << position
390
+ if bind_type == Integer
391
+ @raw_statement.registerReturnParameter(position, java.sql.Types::BIGINT)
392
+ end
393
+ end
394
+
395
+ def exec
396
+ if @exec_sql
397
+ # DDL / PL/SQL block executed via createStatement — never returns rows
398
+ @raw_statement.execute(@exec_sql)
399
+ elsif @raw_statement.execute
400
+ # execute() returns true when the result is a ResultSet (SELECT)
401
+ @raw_result_set = @raw_statement.getResultSet
402
+ end
403
+ true
404
+ end
405
+
406
+ def exec_update
407
+ @raw_statement.executeUpdate
408
+ end
409
+
410
+ def metadata
411
+ @metadata ||= @raw_result_set&.getMetaData
412
+ end
413
+
414
+ def column_types
415
+ return [] unless @raw_result_set
416
+ @column_types ||= (1..metadata.getColumnCount).map { |i| metadata.getColumnTypeName(i).to_sym }
417
+ end
418
+
419
+ def column_names
420
+ return [] unless @raw_result_set
421
+ @column_names ||= (1..metadata.getColumnCount).map { |i| metadata.getColumnName(i) }
422
+ end
423
+ alias :get_col_names :column_names
424
+
425
+ def select_statement?
426
+ !@raw_result_set.nil?
427
+ end
428
+
429
+ def row_count
430
+ @raw_statement.getUpdateCount
431
+ end
432
+
433
+ def fetch(options = {})
434
+ if @raw_result_set.next
435
+ get_lob_value = options[:get_lob_value]
436
+ row_values = []
437
+ column_types.each_with_index do |column_type, i|
438
+ row_values <<
439
+ @raw_connection.get_ruby_value_from_result_set(@raw_result_set, i + 1, column_type, get_lob_value)
440
+ end
441
+ row_values
442
+ else
443
+ @raw_result_set.close
444
+ nil
445
+ end
446
+ end
447
+
448
+ def get_returning_param(position, type)
449
+ rs_position = @returning_positions.index(position) + 1
450
+ rs = @raw_statement.getReturnResultSet
451
+ if rs.next
452
+ # Assuming that primary key will not be larger as long max value
453
+ returning_id = rs.getLong(rs_position)
454
+ rs.wasNull ? nil : returning_id
455
+ else
456
+ nil
457
+ end
458
+ end
459
+
460
+ def close
461
+ @raw_statement.close
462
+ rescue Java::JavaSql::SQLException => e
463
+ # Tolerate closing a statement whose underlying connection or
464
+ # statement has already been torn down (typical after with_retry
465
+ # has reset the connection). Re-raise anything else so genuine
466
+ # driver/resource bugs surface.
467
+ #
468
+ # ORA-17002 "Io exception" - network/socket failure
469
+ # ORA-17008 "Closed Connection" - connection already closed
470
+ # ORA-17009 "Closed Statement" - statement already closed
471
+ raise unless [17_002, 17_008, 17_009].include?(e.getErrorCode)
472
+ nil
473
+ end
474
+ end
475
+
476
+ def select(sql, name = nil, return_column_names = false)
477
+ with_retry do
478
+ select_no_retry(sql, name, return_column_names)
479
+ end
480
+ end
481
+
482
+ def select_no_retry(sql, name = nil, return_column_names = false)
483
+ stmt = @raw_connection.prepareStatement(sql)
484
+ rset = stmt.executeQuery
485
+
486
+ # Reuse the same hash for all rows
487
+ column_hash = {}
488
+
489
+ metadata = rset.getMetaData
490
+ column_count = metadata.getColumnCount
491
+
492
+ cols_types_index = (1..column_count).map do |i|
493
+ col_name = _oracle_downcase(metadata.getColumnName(i))
494
+ next if col_name == "raw_rnum_"
495
+ column_hash[col_name] = nil
496
+ [col_name, metadata.getColumnTypeName(i).to_sym, i]
497
+ end
498
+ cols_types_index.delete(nil)
499
+
500
+ rows = []
501
+ get_lob_value = !(name == "Writable Large Object")
502
+
503
+ while rset.next
504
+ hash = column_hash.dup
505
+ cols_types_index.each do |col, column_type, i|
506
+ hash[col] = get_ruby_value_from_result_set(rset, i, column_type, get_lob_value)
507
+ end
508
+ rows << hash
509
+ end
510
+
511
+ return_column_names ? [rows, cols_types_index.map(&:first)] : rows
512
+ ensure
513
+ rset.close rescue nil
514
+ stmt.close rescue nil
515
+ end
516
+
517
+ def write_lob(lob, value, is_binary = false)
518
+ if is_binary
519
+ lob.setBytes(1, value.to_java_bytes)
520
+ else
521
+ lob.setString(1, value)
522
+ end
523
+ end
524
+
525
+ # To allow private method called from `JDBCConnection`
526
+ def describe(name)
527
+ super
528
+ end
529
+
530
+ # Return java.sql.SQLException error code
531
+ def error_code(exception)
532
+ case exception
533
+ when Java::JavaSql::SQLException
534
+ exception.getErrorCode
535
+ else
536
+ nil
537
+ end
538
+ end
539
+
540
+ def get_ruby_value_from_result_set(rset, i, type_name, get_lob_value = true)
541
+ case type_name
542
+ when :NUMBER
543
+ d = rset.getNUMBER(i)
544
+ if d.nil?
545
+ nil
546
+ elsif d.isInt
547
+ Integer(d.stringValue)
548
+ else
549
+ BigDecimal(d.stringValue)
550
+ end
551
+ when :BINARY_FLOAT
552
+ rset.getFloat(i)
553
+ when :VARCHAR2, :LONG, :NVARCHAR2
554
+ rset.getString(i)
555
+ when :CHAR, :NCHAR
556
+ char_str = rset.getString(i)
557
+ if !char_str.nil?
558
+ char_str.rstrip
559
+ end
560
+ when :DATE
561
+ if dt = rset.getDATE(i)
562
+ d = dt.dateValue
563
+ t = dt.timeValue
564
+ Time.send(ActiveRecord.default_timezone, d.year + 1900, d.month + 1, d.date, t.hours, t.minutes, t.seconds)
565
+ else
566
+ nil
567
+ end
568
+ when :TIMESTAMP
569
+ # Oracle JDBC getTimestamp for plain TIMESTAMP uses the JVM default timezone,
570
+ # but we store values using the session timezone. Pass a matching Calendar so
571
+ # the driver interprets the stored wall-clock value in the session timezone.
572
+ tz_id = @session_time_zone || java.util.TimeZone.default.getID
573
+ cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone(tz_id))
574
+ ts = rset.getTimestamp(i, cal)
575
+ if ts
576
+ instant = ts.toInstant
577
+ t = Time.at(instant.getEpochSecond, instant.getNano, :nanosecond)
578
+ ActiveRecord.default_timezone == :utc ? t.utc : t.localtime
579
+ end
580
+ when :TIMESTAMPTZ, :TIMESTAMPLTZ, :"TIMESTAMP WITH TIME ZONE", :"TIMESTAMP WITH LOCAL TIME ZONE"
581
+ # TIMESTAMPTZ/TIMESTAMPLTZ include timezone info so getTimestamp returns
582
+ # the correct UTC epoch without needing a Calendar.
583
+ ts = rset.getTimestamp(i)
584
+ if ts
585
+ instant = ts.toInstant
586
+ t = Time.at(instant.getEpochSecond, instant.getNano, :nanosecond)
587
+ ActiveRecord.default_timezone == :utc ? t.utc : t.localtime
588
+ end
589
+ when :CLOB
590
+ get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
591
+ when :NCLOB
592
+ get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
593
+ when :BLOB
594
+ get_lob_value ? lob_to_ruby_value(rset.getBlob(i)) : rset.getBlob(i)
595
+ when :RAW
596
+ raw_value = rset.getRAW(i)
597
+ raw_value && raw_value.getBytes.to_a.pack("C*")
598
+ else
599
+ nil
600
+ end
601
+ end
602
+
603
+ private
604
+ def lob_to_ruby_value(val)
605
+ case val
606
+ when ::Java::OracleSql::CLOB
607
+ if val.isEmptyLob
608
+ nil
609
+ else
610
+ val.getSubString(1, val.length)
611
+ end
612
+ when ::Java::OracleSql::NCLOB
613
+ if val.isEmptyLob
614
+ nil
615
+ else
616
+ val.getSubString(1, val.length)
617
+ end
618
+ when ::Java::OracleSql::BLOB
619
+ if val.isEmptyLob
620
+ nil
621
+ else
622
+ String.from_java_bytes(val.getBytes(1, val.length))
623
+ end
624
+ end
625
+ end
626
+ end
627
+ end
628
+ end
629
+ 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