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,57 @@
|
|
|
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_create :record_lobs_for_create
|
|
15
|
+
after_create :enhanced_write_lobs
|
|
16
|
+
before_update :record_changed_lobs
|
|
17
|
+
after_update :enhanced_write_lobs
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ClassMethods
|
|
21
|
+
def lob_columns
|
|
22
|
+
columns.select do |column|
|
|
23
|
+
column.sql_type_metadata.sql_type.end_with?("LOB")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
def enhanced_write_lobs
|
|
30
|
+
# Skip if using prepared statements - LOB data is already written via temp LOB binding
|
|
31
|
+
return if self.class.connection.prepared_statements
|
|
32
|
+
|
|
33
|
+
if self.class.connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
|
|
34
|
+
!(self.class.custom_create_method || self.class.custom_update_method)
|
|
35
|
+
self.class.connection.write_lobs(self.class.table_name, self.class, attributes, @changed_lob_columns)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def record_lobs_for_create
|
|
40
|
+
@changed_lob_columns = self.class.lob_columns.select do |col|
|
|
41
|
+
!attributes[col.name].nil? && !self.class.readonly_attributes.to_a.include?(col.name)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def record_changed_lobs
|
|
46
|
+
@changed_lob_columns = self.class.lob_columns.select do |col|
|
|
47
|
+
self.will_save_change_to_attribute?(col.name) && !self.class.readonly_attributes.to_a.include?(col.name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
ActiveSupport.on_load(:active_record) do
|
|
56
|
+
ActiveRecord::Base.send(:include, ActiveRecord::ConnectionAdapters::OracleEnhanced::Lob)
|
|
57
|
+
end
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require "oci8"
|
|
7
|
+
rescue LoadError => e
|
|
8
|
+
# OCI8 driver is unavailable or failed to load a required library.
|
|
9
|
+
raise LoadError, "ERROR: '#{e.message}'. "\
|
|
10
|
+
"ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. "\
|
|
11
|
+
"You may need install ruby-oci8 gem."
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# check ruby-oci8 version
|
|
15
|
+
required_oci8_version = [2, 2, 4]
|
|
16
|
+
oci8_version_ints = OCI8::VERSION.scan(/\d+/).map { |s| s.to_i }
|
|
17
|
+
if (oci8_version_ints <=> required_oci8_version) < 0
|
|
18
|
+
$stderr.puts <<~EOS
|
|
19
|
+
"ERROR: ruby-oci8 version #{OCI8::VERSION} is too old. Please install ruby-oci8 version #{required_oci8_version.join('.')} or later."
|
|
20
|
+
EOS
|
|
21
|
+
|
|
22
|
+
exit!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module ActiveRecord
|
|
26
|
+
module ConnectionAdapters
|
|
27
|
+
# OCI database interface for MRI
|
|
28
|
+
module OracleEnhanced
|
|
29
|
+
class OCIConnection < OracleEnhanced::Connection # :nodoc:
|
|
30
|
+
def initialize(config)
|
|
31
|
+
@raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory)
|
|
32
|
+
# default schema owner
|
|
33
|
+
@owner = config[:schema]
|
|
34
|
+
@owner ||= config[:username]
|
|
35
|
+
@owner = @owner.to_s.upcase
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def raw_oci_connection
|
|
39
|
+
if @raw_connection.is_a? OCI8
|
|
40
|
+
@raw_connection
|
|
41
|
+
# ActiveRecord Oracle enhanced adapter puts OCI8EnhancedAutoRecover wrapper around OCI8
|
|
42
|
+
# in this case we need to pass original OCI8 connection
|
|
43
|
+
else
|
|
44
|
+
@raw_connection.instance_variable_get(:@raw_connection)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def auto_retry
|
|
49
|
+
@raw_connection.auto_retry if @raw_connection
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def auto_retry=(value)
|
|
53
|
+
@raw_connection.auto_retry = value if @raw_connection
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def logoff
|
|
57
|
+
@raw_connection.logoff
|
|
58
|
+
@raw_connection.active = false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def commit
|
|
62
|
+
@raw_connection.commit
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def rollback
|
|
66
|
+
@raw_connection.rollback
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def autocommit?
|
|
70
|
+
@raw_connection.autocommit?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def autocommit=(value)
|
|
74
|
+
@raw_connection.autocommit = value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Checks connection, returns true if active. Note that ping actively
|
|
78
|
+
# checks the connection, while #active? simply returns the last
|
|
79
|
+
# known state.
|
|
80
|
+
def ping
|
|
81
|
+
@raw_connection.ping
|
|
82
|
+
rescue OCIException => e
|
|
83
|
+
raise OracleEnhanced::ConnectionException, e.message
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def active?
|
|
87
|
+
@raw_connection.active?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def reset
|
|
91
|
+
@raw_connection.reset
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def reset!
|
|
95
|
+
@raw_connection.reset!
|
|
96
|
+
rescue OCIException => e
|
|
97
|
+
raise OracleEnhanced::ConnectionException, e.message
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def exec(sql, *bindvars, allow_retry: false, &block)
|
|
101
|
+
with_retry(allow_retry: allow_retry) { @raw_connection.exec(sql, *bindvars, &block) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def with_retry(allow_retry: false, &block)
|
|
105
|
+
@raw_connection.with_retry(allow_retry: allow_retry, &block)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def prepare(sql)
|
|
109
|
+
Cursor.new(self, @raw_connection.parse(sql))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class Cursor
|
|
113
|
+
def initialize(connection, raw_cursor)
|
|
114
|
+
@raw_connection = connection
|
|
115
|
+
@raw_cursor = raw_cursor
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def bind_params(*bind_vars)
|
|
119
|
+
index = 1
|
|
120
|
+
bind_vars.flatten.each do |var|
|
|
121
|
+
if Hash === var
|
|
122
|
+
var.each { |key, val| bind_param key, val }
|
|
123
|
+
else
|
|
124
|
+
bind_param index, var
|
|
125
|
+
index += 1
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def bind_param(position, value)
|
|
131
|
+
case value
|
|
132
|
+
when Type::OracleEnhanced::Raw
|
|
133
|
+
@raw_cursor.bind_param(position, OracleEnhanced::Quoting.encode_raw(value))
|
|
134
|
+
when ActiveModel::Type::Decimal
|
|
135
|
+
@raw_cursor.bind_param(position, BigDecimal(value.to_s))
|
|
136
|
+
when Type::OracleEnhanced::CharacterString::Data
|
|
137
|
+
@raw_cursor.bind_param(position, value.to_character_str)
|
|
138
|
+
when NilClass
|
|
139
|
+
@raw_cursor.bind_param(position, nil, String)
|
|
140
|
+
else
|
|
141
|
+
@raw_cursor.bind_param(position, value)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def bind_returning_param(position, bind_type)
|
|
146
|
+
@raw_cursor.bind_param(position, nil, bind_type)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def exec
|
|
150
|
+
@raw_cursor.exec
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def exec_update
|
|
154
|
+
@raw_cursor.exec
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def get_col_names
|
|
158
|
+
@raw_cursor.get_col_names
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def row_count
|
|
162
|
+
@raw_cursor.row_count
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def select_statement?
|
|
166
|
+
@raw_cursor.type == :select_stmt
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def fetch(options = {})
|
|
170
|
+
if row = @raw_cursor.fetch
|
|
171
|
+
get_lob_value = options[:get_lob_value]
|
|
172
|
+
col_index = 0
|
|
173
|
+
row.map do |col|
|
|
174
|
+
col_value = @raw_connection.typecast_result_value(col, get_lob_value)
|
|
175
|
+
col_metadata = @raw_cursor.column_metadata.fetch(col_index)
|
|
176
|
+
if !col_metadata.nil?
|
|
177
|
+
key = col_metadata.data_type
|
|
178
|
+
case key.to_s.downcase
|
|
179
|
+
when "char"
|
|
180
|
+
col_value = col.to_s.rstrip
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
col_index = col_index + 1
|
|
184
|
+
col_value
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def get_returning_param(position, type)
|
|
190
|
+
@raw_cursor[position]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def close
|
|
194
|
+
@raw_cursor.close
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def select(sql, name = nil, return_column_names = false)
|
|
199
|
+
cursor = @raw_connection.exec(sql)
|
|
200
|
+
cols = []
|
|
201
|
+
# Ignore raw_rnum_ which is used to simulate LIMIT and OFFSET
|
|
202
|
+
cursor.get_col_names.each do |col_name|
|
|
203
|
+
col_name = _oracle_downcase(col_name)
|
|
204
|
+
cols << col_name unless col_name == "raw_rnum_"
|
|
205
|
+
end
|
|
206
|
+
# Reuse the same hash for all rows
|
|
207
|
+
column_hash = {}
|
|
208
|
+
cols.each { |c| column_hash[c] = nil }
|
|
209
|
+
rows = []
|
|
210
|
+
get_lob_value = !(name == "Writable Large Object")
|
|
211
|
+
|
|
212
|
+
while row = cursor.fetch
|
|
213
|
+
hash = column_hash.dup
|
|
214
|
+
|
|
215
|
+
cols.each_with_index do |col, i|
|
|
216
|
+
col_value = typecast_result_value(row[i], get_lob_value)
|
|
217
|
+
col_metadata = cursor.column_metadata.fetch(i)
|
|
218
|
+
if !col_metadata.nil?
|
|
219
|
+
key = col_metadata.data_type
|
|
220
|
+
case key.to_s.downcase
|
|
221
|
+
when "char"
|
|
222
|
+
col_value = col_value.to_s.rstrip
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
hash[col] = col_value
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
rows << hash
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
return_column_names ? [rows, cols] : rows
|
|
232
|
+
ensure
|
|
233
|
+
cursor.close if cursor
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def write_lob(lob, value, is_binary = false)
|
|
237
|
+
lob.write value
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def describe(name)
|
|
241
|
+
super
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Return OCIError error code
|
|
245
|
+
def error_code(exception)
|
|
246
|
+
case exception
|
|
247
|
+
when OCIError
|
|
248
|
+
exception.code
|
|
249
|
+
else
|
|
250
|
+
nil
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def typecast_result_value(value, get_lob_value)
|
|
255
|
+
case value
|
|
256
|
+
when Integer
|
|
257
|
+
value
|
|
258
|
+
when String
|
|
259
|
+
value
|
|
260
|
+
when Float, BigDecimal
|
|
261
|
+
# return Integer if value is integer (to avoid issues with _before_type_cast values for id attributes)
|
|
262
|
+
value == (v_to_i = value.to_i) ? v_to_i : value
|
|
263
|
+
when OCI8::LOB
|
|
264
|
+
if get_lob_value
|
|
265
|
+
data = value.read || "" # if value.read returns nil, then we have an empty_clob() i.e. an empty string
|
|
266
|
+
# In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
|
|
267
|
+
data.force_encoding("ASCII-8BIT") if data.respond_to?(:force_encoding) && value.is_a?(OCI8::BLOB)
|
|
268
|
+
data
|
|
269
|
+
else
|
|
270
|
+
value
|
|
271
|
+
end
|
|
272
|
+
when Time, DateTime
|
|
273
|
+
create_time_with_default_timezone(value)
|
|
274
|
+
else
|
|
275
|
+
value
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def database_version
|
|
280
|
+
@database_version ||= (version = raw_connection.oracle_server_version) && [version.major, version.minor]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private
|
|
284
|
+
def date_without_time?(value)
|
|
285
|
+
case value
|
|
286
|
+
when OraDate
|
|
287
|
+
value.hour == 0 && value.minute == 0 && value.second == 0
|
|
288
|
+
else
|
|
289
|
+
value.hour == 0 && value.min == 0 && value.sec == 0
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def create_time_with_default_timezone(value)
|
|
294
|
+
year, month, day, hour, min, sec, usec = case value
|
|
295
|
+
when Time
|
|
296
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec, value.usec]
|
|
297
|
+
when OraDate
|
|
298
|
+
[value.year, value.month, value.day, value.hour, value.minute, value.second, 0]
|
|
299
|
+
else
|
|
300
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec, 0]
|
|
301
|
+
end
|
|
302
|
+
# code from Time.time_with_datetime_fallback
|
|
303
|
+
begin
|
|
304
|
+
Time.send(ActiveRecord.default_timezone, year, month, day, hour, min, sec, usec)
|
|
305
|
+
rescue
|
|
306
|
+
offset = ActiveRecord.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
|
|
307
|
+
::DateTime.civil(year, month, day, hour, min, sec, offset)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# The OracleEnhancedOCIFactory factors out the code necessary to connect and
|
|
313
|
+
# configure an Oracle/OCI connection.
|
|
314
|
+
class OracleEnhancedOCIFactory # :nodoc:
|
|
315
|
+
DEFAULT_TCP_KEEPALIVE_TIME = 600
|
|
316
|
+
|
|
317
|
+
def self.new_connection(config)
|
|
318
|
+
# to_s needed if username, password or database is specified as number in database.yml file
|
|
319
|
+
username = config[:username] && config[:username].to_s
|
|
320
|
+
password = config[:password] && config[:password].to_s
|
|
321
|
+
database = config[:database] && config[:database].to_s
|
|
322
|
+
schema = config[:schema] && config[:schema].to_s
|
|
323
|
+
host, port = config[:host], config[:port]
|
|
324
|
+
privilege = config[:privilege] && config[:privilege].to_sym
|
|
325
|
+
async = config[:allow_concurrency]
|
|
326
|
+
prefetch_rows = config[:prefetch_rows] || 100
|
|
327
|
+
cursor_sharing = config[:cursor_sharing] || "force"
|
|
328
|
+
# get session time_zone from configuration or from TZ environment variable
|
|
329
|
+
time_zone = config[:time_zone] || ENV["TZ"]
|
|
330
|
+
|
|
331
|
+
# using a connection string via DATABASE_URL
|
|
332
|
+
connection_string = if host == "connection-string"
|
|
333
|
+
database
|
|
334
|
+
# connection using host, port and database name
|
|
335
|
+
elsif host || port
|
|
336
|
+
host ||= "localhost"
|
|
337
|
+
host = "[#{host}]" if /^[^\[].*:/.match?(host) # IPv6
|
|
338
|
+
port ||= 1521
|
|
339
|
+
database = "/#{database}" unless database.start_with?("/")
|
|
340
|
+
"//#{host}:#{port}#{database}"
|
|
341
|
+
# if no host is specified then assume that
|
|
342
|
+
# database parameter is TNS alias or TNS connection string
|
|
343
|
+
else
|
|
344
|
+
database
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
OCI8.properties[:tcp_keepalive] = config[:tcp_keepalive] == false ? false : true
|
|
348
|
+
begin
|
|
349
|
+
OCI8.properties[:tcp_keepalive_time] = config[:tcp_keepalive_time] || DEFAULT_TCP_KEEPALIVE_TIME
|
|
350
|
+
rescue NotImplementedError
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
conn = OCI8.new username, password, connection_string, privilege
|
|
354
|
+
conn.autocommit = true
|
|
355
|
+
conn.non_blocking = true if async
|
|
356
|
+
conn.prefetch_rows = prefetch_rows
|
|
357
|
+
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil if cursor_sharing
|
|
358
|
+
if ActiveRecord.default_timezone == :local
|
|
359
|
+
conn.exec "alter session set time_zone = '#{time_zone}'" unless time_zone.blank?
|
|
360
|
+
elsif ActiveRecord.default_timezone == :utc
|
|
361
|
+
conn.exec "alter session set time_zone = '+00:00'"
|
|
362
|
+
end
|
|
363
|
+
conn.exec "alter session set current_schema = #{schema}" unless schema.blank?
|
|
364
|
+
|
|
365
|
+
# Initialize NLS parameters
|
|
366
|
+
OracleEnhancedAdapter::DEFAULT_NLS_PARAMETERS.each do |key, default_value|
|
|
367
|
+
value = config[key] || ENV[key.to_s.upcase] || default_value
|
|
368
|
+
if value
|
|
369
|
+
conn.exec "alter session set #{key} = '#{value}'"
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
OracleEnhancedAdapter::FIXED_NLS_PARAMETERS.each do |key, value|
|
|
374
|
+
conn.exec "alter session set #{key} = '#{value}'"
|
|
375
|
+
end
|
|
376
|
+
conn
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
|
|
384
|
+
# reset functionality. If a call to #exec fails, and autocommit is turned on
|
|
385
|
+
# (ie., we're not in the middle of a longer transaction), it will
|
|
386
|
+
# automatically reconnect and try again. If autocommit is turned off,
|
|
387
|
+
# this would be dangerous (as the earlier part of the implied transaction
|
|
388
|
+
# may have failed silently if the connection died) -- so instead the
|
|
389
|
+
# connection is marked as dead, to be reconnected on it's next use.
|
|
390
|
+
# :stopdoc:
|
|
391
|
+
class OCI8EnhancedAutoRecover < DelegateClass(OCI8) # :nodoc:
|
|
392
|
+
attr_accessor :active # :nodoc:
|
|
393
|
+
alias :active? :active # :nodoc:
|
|
394
|
+
|
|
395
|
+
cattr_accessor :auto_retry
|
|
396
|
+
class << self
|
|
397
|
+
alias :auto_retry? :auto_retry # :nodoc:
|
|
398
|
+
end
|
|
399
|
+
@@auto_retry = false
|
|
400
|
+
|
|
401
|
+
def initialize(config, factory) # :nodoc:
|
|
402
|
+
@active = true
|
|
403
|
+
@config = config
|
|
404
|
+
@factory = factory
|
|
405
|
+
@raw_connection = @factory.new_connection @config
|
|
406
|
+
super @raw_connection
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Checks connection, returns true if active. Note that ping actively
|
|
410
|
+
# checks the connection, while #active? simply returns the last
|
|
411
|
+
# known state.
|
|
412
|
+
def ping # :nodoc:
|
|
413
|
+
@raw_connection.exec("select 1 from dual") { |r| nil }
|
|
414
|
+
@active = true
|
|
415
|
+
rescue
|
|
416
|
+
@active = false
|
|
417
|
+
raise
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def reset
|
|
421
|
+
# tentative
|
|
422
|
+
reset!
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Resets connection, by logging off and creating a new connection.
|
|
426
|
+
def reset! # :nodoc:
|
|
427
|
+
logoff rescue nil
|
|
428
|
+
begin
|
|
429
|
+
@raw_connection = @factory.new_connection @config
|
|
430
|
+
__setobj__ @raw_connection
|
|
431
|
+
@active = true
|
|
432
|
+
rescue
|
|
433
|
+
@active = false
|
|
434
|
+
raise
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# ORA-00028: your session has been killed
|
|
439
|
+
# ORA-01012: not logged on
|
|
440
|
+
# ORA-03113: end-of-file on communication channel
|
|
441
|
+
# ORA-03114: not connected to ORACLE
|
|
442
|
+
# ORA-03135: connection lost contact
|
|
443
|
+
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ] # :nodoc:
|
|
444
|
+
|
|
445
|
+
# Adds auto-recovery functionality.
|
|
446
|
+
def with_retry(allow_retry: false) # :nodoc:
|
|
447
|
+
should_retry = (allow_retry || self.class.auto_retry?) && autocommit?
|
|
448
|
+
|
|
449
|
+
begin
|
|
450
|
+
yield
|
|
451
|
+
rescue OCIException => e
|
|
452
|
+
raise unless e.is_a?(OCIError) && LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
|
453
|
+
@active = false
|
|
454
|
+
raise unless should_retry
|
|
455
|
+
should_retry = false
|
|
456
|
+
reset! rescue nil
|
|
457
|
+
retry
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def exec(sql, *bindvars, &block) # :nodoc:
|
|
462
|
+
with_retry { @raw_connection.exec(sql, *bindvars, &block) }
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
# :startdoc:
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module OracleEnhanced
|
|
6
|
+
module OCIQuoting
|
|
7
|
+
def type_cast(value)
|
|
8
|
+
case value
|
|
9
|
+
when ActiveModel::Type::Binary::Data
|
|
10
|
+
lob_value = value == "" ? " " : value
|
|
11
|
+
bind_type = OCI8::BLOB
|
|
12
|
+
ora_value = bind_type.new(_connection.raw_oci_connection, lob_value)
|
|
13
|
+
ora_value.size = 0 if value == ""
|
|
14
|
+
ora_value
|
|
15
|
+
when Type::OracleEnhanced::Text::Data
|
|
16
|
+
lob_value = value.to_s == "" ? " " : value.to_s
|
|
17
|
+
bind_type = OCI8::CLOB
|
|
18
|
+
ora_value = bind_type.new(_connection.raw_oci_connection, lob_value)
|
|
19
|
+
ora_value.size = 0 if value.to_s == ""
|
|
20
|
+
ora_value
|
|
21
|
+
when Type::OracleEnhanced::NationalCharacterText::Data
|
|
22
|
+
lob_value = value.to_s == "" ? " " : value.to_s
|
|
23
|
+
bind_type = OCI8::NCLOB
|
|
24
|
+
ora_value = bind_type.new(_connection.raw_oci_connection, lob_value)
|
|
25
|
+
ora_value.size = 0 if value.to_s == ""
|
|
26
|
+
ora_value
|
|
27
|
+
else
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module ActiveRecord
|
|
37
|
+
module ConnectionAdapters
|
|
38
|
+
module OracleEnhanced
|
|
39
|
+
module Quoting
|
|
40
|
+
prepend OCIQuoting
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|