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.
- checksums.yaml +7 -0
- data/History.md +1971 -0
- data/License.txt +20 -0
- data/README.md +947 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +7 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +24 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +137 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +359 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +47 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +325 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +63 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +71 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +629 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +57 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +465 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +195 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +186 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +99 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +197 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +739 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +394 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +886 -0
- data/lib/active_record/type/oracle_enhanced/boolean.rb +19 -0
- data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
- data/lib/active_record/type/oracle_enhanced/integer.rb +14 -0
- data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
- data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
- data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
- data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
- data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
- data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
- data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
- data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
- data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
- data/lib/arel/visitors/oracle.rb +216 -0
- data/lib/arel/visitors/oracle12.rb +121 -0
- data/lib/arel/visitors/oracle_common.rb +51 -0
- data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +24 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +40 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/composite_spec.rb +84 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +589 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/dbconsole_spec.rb +63 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1318 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +485 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
- data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
- data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
- data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
- data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
- data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
- data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
- data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
- data/spec/active_record/oracle_enhanced/type/integer_spec.rb +101 -0
- data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
- data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
- data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
- data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
- data/spec/active_record/oracle_enhanced/type/text_spec.rb +295 -0
- data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +107 -0
- data/spec/spec_config.yaml.template +11 -0
- data/spec/spec_helper.rb +225 -0
- data/spec/support/alter_system_set_open_cursors.sql +1 -0
- data/spec/support/alter_system_user_password.sql +2 -0
- data/spec/support/create_oracle_enhanced_users.sql +31 -0
- 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
|