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,865 @@
1
+ # frozen_string_literal: true
2
+
3
+ # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 10g, 11g and 12c
4
+ #
5
+ # Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
6
+ #
7
+ # Current maintainer: Raimonds Simanovskis (http://blog.rayapps.com)
8
+ #
9
+ #########################################################################
10
+ #
11
+ # See History.md for changes added to original oracle_adapter.rb
12
+ #
13
+ #########################################################################
14
+ #
15
+ # From original oracle_adapter.rb:
16
+ #
17
+ # Implementation notes:
18
+ # 1. Redefines (safely) a method in ActiveRecord to make it possible to
19
+ # implement an autonumbering solution for Oracle.
20
+ # 2. The OCI8 driver is patched to properly handle values for LONG and
21
+ # TIMESTAMP columns. The driver-author has indicated that a future
22
+ # release of the driver will obviate this patch.
23
+ # 3. LOB support is implemented through an after_save callback.
24
+ # 4. Oracle does not offer native LIMIT and OFFSET options; this
25
+ # functionality is mimiced through the use of nested selects.
26
+ # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
27
+ #
28
+ # Do what you want with this code, at your own peril, but if any
29
+ # significant portion of my code remains then please acknowledge my
30
+ # contribution.
31
+ # portions Copyright 2005 Graham Jenkins
32
+
33
+ require "arel/visitors/oracle"
34
+ require "arel/visitors/oracle12"
35
+ require "active_record/connection_adapters"
36
+ require "active_record/connection_adapters/abstract_adapter"
37
+ require "active_record/connection_adapters/statement_pool"
38
+ require "active_record/connection_adapters/oracle_enhanced/connection"
39
+ require "active_record/connection_adapters/oracle_enhanced/database_statements"
40
+ require "active_record/connection_adapters/oracle_enhanced/schema_creation"
41
+ require "active_record/connection_adapters/oracle_enhanced/schema_definitions"
42
+ require "active_record/connection_adapters/oracle_enhanced/schema_dumper"
43
+ require "active_record/connection_adapters/oracle_enhanced/schema_statements"
44
+ require "active_record/connection_adapters/oracle_enhanced/context_index"
45
+ require "active_record/connection_adapters/oracle_enhanced/column"
46
+ require "active_record/connection_adapters/oracle_enhanced/quoting"
47
+ require "active_record/connection_adapters/oracle_enhanced/database_limits"
48
+ require "active_record/connection_adapters/oracle_enhanced/dbms_output"
49
+ require "active_record/connection_adapters/oracle_enhanced/type_metadata"
50
+ require "active_record/connection_adapters/oracle_enhanced/structure_dump"
51
+ require "active_record/connection_adapters/oracle_enhanced/lob"
52
+
53
+ require "active_record/type/oracle_enhanced/raw"
54
+ require "active_record/type/oracle_enhanced/integer"
55
+ require "active_record/type/oracle_enhanced/string"
56
+ require "active_record/type/oracle_enhanced/national_character_string"
57
+ require "active_record/type/oracle_enhanced/text"
58
+ require "active_record/type/oracle_enhanced/national_character_text"
59
+ require "active_record/type/oracle_enhanced/boolean"
60
+ require "active_record/type/oracle_enhanced/json"
61
+ require "active_record/type/oracle_enhanced/timestamptz"
62
+ require "active_record/type/oracle_enhanced/timestampltz"
63
+ require "active_record/type/oracle_enhanced/character_string"
64
+
65
+ module ActiveRecord
66
+ module ConnectionAdapters # :nodoc:
67
+ # Oracle enhanced adapter will work with both
68
+ # CRuby ruby-oci8 gem (which provides interface to Oracle OCI client)
69
+ # or with JRuby and Oracle JDBC driver.
70
+ #
71
+ # It should work with Oracle 10g, 11g and 12c databases.
72
+ #
73
+ # Usage notes:
74
+ # * Key generation assumes a "${table_name}_seq" sequence is available
75
+ # for all tables; the sequence name can be changed using
76
+ # ActiveRecord::Base.set_sequence_name. When using Migrations, these
77
+ # sequences are created automatically.
78
+ # Use set_sequence_name :autogenerated with legacy tables that have
79
+ # triggers that populate primary keys automatically.
80
+ # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
81
+ # Consequently some hacks are employed to map data back to Date or Time
82
+ # in Ruby. Timezones and sub-second precision on timestamps are
83
+ # not supported.
84
+ # * Default values that are functions (such as "SYSDATE") are not
85
+ # supported. This is a restriction of the way ActiveRecord supports
86
+ # default values.
87
+ #
88
+ # Required parameters:
89
+ #
90
+ # * <tt>:username</tt>
91
+ # * <tt>:password</tt>
92
+ # * <tt>:database</tt> - either TNS alias or connection string for OCI client or database name in JDBC connection string
93
+ #
94
+ # Optional parameters:
95
+ #
96
+ # * <tt>:host</tt> - host name for JDBC connection, defaults to "localhost"
97
+ # * <tt>:port</tt> - port number for JDBC connection, defaults to 1521
98
+ # * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
99
+ # * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
100
+ # * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
101
+ # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, no default value
102
+ # * <tt>:time_zone</tt> - database session time zone
103
+ # (it is recommended to set it using ENV['TZ'] which will be then also used for database session time zone)
104
+ # * <tt>:schema</tt> - database schema which holds schema objects.
105
+ # * <tt>:tcp_keepalive</tt> - TCP keepalive is enabled for OCI client, defaults to true
106
+ # * <tt>:tcp_keepalive_time</tt> - TCP keepalive time for OCI client, defaults to 600
107
+ # * <tt>:jdbc_statement_cache_size</tt> - number of cached SQL cursors to keep open, disabled per default (for unpooled JDBC only)
108
+ # * <tt>:jdbc_connect_properties</tt> - Additional properties for establishing Oracle JDBC connection (for unpooled JDBC only)
109
+ # example to require encryption and checksumming for network connection:
110
+ # adapter: oracle_enhanced
111
+ # jdbc_connect_properties:
112
+ # 'oracle.net.encryption_client': REQUIRED
113
+ # 'oracle.net.crypto_checksum_client': REQUIRED
114
+ #
115
+ # Optionals NLS parameters:
116
+ #
117
+ # * <tt>:nls_calendar</tt>
118
+ # * <tt>:nls_comp</tt>
119
+ # * <tt>:nls_currency</tt>
120
+ # * <tt>:nls_date_language</tt>
121
+ # * <tt>:nls_dual_currency</tt>
122
+ # * <tt>:nls_iso_currency</tt>
123
+ # * <tt>:nls_language</tt>
124
+ # * <tt>:nls_length_semantics</tt> - semantics of size of VARCHAR2 and CHAR columns, defaults to <tt>CHAR</tt>
125
+ # (meaning that size specifies number of characters and not bytes)
126
+ # * <tt>:nls_nchar_conv_excp</tt>
127
+ # * <tt>:nls_numeric_characters</tt>
128
+ # * <tt>:nls_sort</tt>
129
+ # * <tt>:nls_territory</tt>
130
+ # * <tt>:nls_timestamp_tz_format</tt>
131
+ # * <tt>:nls_time_format</tt>
132
+ # * <tt>:nls_time_tz_format</tt>
133
+ #
134
+ # Fixed NLS values (not overridable):
135
+ #
136
+ # * <tt>:nls_date_format</tt> - format for :date columns is <tt>YYYY-MM-DD HH24:MI:SS</tt>
137
+ # * <tt>:nls_timestamp_format</tt> - format for :timestamp columns is <tt>YYYY-MM-DD HH24:MI:SS:FF6</tt>
138
+ #
139
+ class OracleEnhancedAdapter < AbstractAdapter
140
+ include OracleEnhanced::DatabaseStatements
141
+ include OracleEnhanced::SchemaStatements
142
+ include OracleEnhanced::ContextIndex
143
+ include OracleEnhanced::Quoting
144
+ include OracleEnhanced::DatabaseLimits
145
+ include OracleEnhanced::DbmsOutput
146
+ include OracleEnhanced::StructureDump
147
+
148
+ ##
149
+ # :singleton-method:
150
+ # By default, the OracleEnhancedAdapter will consider all columns of type <tt>NUMBER(1)</tt>
151
+ # as boolean. If you wish to disable this emulation you can add the following line
152
+ # to your initializer file:
153
+ #
154
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
155
+ cattr_accessor :emulate_booleans
156
+ self.emulate_booleans = true
157
+
158
+ ##
159
+ # :singleton-method:
160
+ # OracleEnhancedAdapter will use the default tablespace, but if you want specific types of
161
+ # objects to go into specific tablespaces, specify them like this in an initializer:
162
+ #
163
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces =
164
+ # {:clob => 'TS_LOB', :blob => 'TS_LOB', :index => 'TS_INDEX', :table => 'TS_DATA'}
165
+ #
166
+ # Using the :tablespace option where available (e.g create_table) will take precedence
167
+ # over these settings.
168
+ cattr_accessor :default_tablespaces
169
+ self.default_tablespaces = {}
170
+
171
+ ##
172
+ # :singleton-method:
173
+ # If you wish that CHAR(1), VARCHAR2(1) columns are typecasted to booleans
174
+ # then you can add the following line to your initializer file:
175
+ #
176
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
177
+ cattr_accessor :emulate_booleans_from_strings
178
+ self.emulate_booleans_from_strings = false
179
+
180
+ ##
181
+ # :singleton-method:
182
+ # By default, OracleEnhanced adapter will use Oracle12 visitor
183
+ # if database version is Oracle 12.1.
184
+ # If you wish to use Oracle visitor which is intended to work with Oracle 11.2 or lower
185
+ # for Oracle 12.1 database you can add the following line to your initializer file:
186
+ #
187
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.use_old_oracle_visitor = true
188
+ cattr_accessor :use_old_oracle_visitor
189
+ self.use_old_oracle_visitor = false
190
+
191
+ ##
192
+ # :singleton-method:
193
+ # Specify default sequence start with value (by default 1 if not explicitly set), e.g.:
194
+ #
195
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 10000
196
+ cattr_accessor :default_sequence_start_value
197
+ self.default_sequence_start_value = 1
198
+
199
+ ##
200
+ # :singleton-method:
201
+ # By default, OracleEnhanced adapter will use longer 128 bytes identifier
202
+ # if database version is Oracle 12.2 or higher.
203
+ # If you wish to use shorter 30 byte identifier with Oracle Database supporting longer identifier
204
+ # you can add the following line to your initializer file:
205
+ #
206
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.use_shorter_identifier = true
207
+ cattr_accessor :use_shorter_identifier
208
+ self.use_shorter_identifier = false
209
+
210
+ ##
211
+ # :singleton-method:
212
+ # By default, OracleEnhanced adapter will grant unlimited tablespace, create session, create table, create view,
213
+ # and create sequence when running the rake task db:create.
214
+ #
215
+ # If you wish to change these permissions you can add the following line to your initializer file:
216
+ #
217
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.permissions =
218
+ # ["create session", "create table", "create view", "create sequence", "create trigger", "ctxapp"]
219
+ cattr_accessor :permissions
220
+ self.permissions = ["unlimited tablespace", "create session", "create table", "create view", "create sequence"]
221
+
222
+ ##
223
+ # :singleton-method:
224
+ # Specify default sequence start with value (by default 1 if not explicitly set), e.g.:
225
+
226
+ class StatementPool < ConnectionAdapters::StatementPool
227
+ private
228
+ def dealloc(stmt)
229
+ stmt.close
230
+ end
231
+ end
232
+
233
+ def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc:
234
+ super(config_or_deprecated_connection, deprecated_logger, deprecated_connection_options, deprecated_config)
235
+
236
+ @raw_connection = ConnectionAdapters::OracleEnhanced::Connection.create(@config)
237
+ @enable_dbms_output = false
238
+ @do_not_prefetch_primary_key = {}
239
+ @columns_cache = {}
240
+ end
241
+
242
+ ADAPTER_NAME = "OracleEnhanced"
243
+
244
+ def adapter_name # :nodoc:
245
+ ADAPTER_NAME
246
+ end
247
+
248
+ # Oracle enhanced adapter has no implementation because
249
+ # Oracle Database cannot detect `NoDatabaseError`.
250
+ # Please refer to the following discussion for details.
251
+ # https://github.com/rsim/oracle-enhanced/pull/1900
252
+ def self.database_exists?(config)
253
+ raise NotImplementedError
254
+ end
255
+
256
+ def arel_visitor # :nodoc:
257
+ if supports_fetch_first_n_rows_and_offset?
258
+ Arel::Visitors::Oracle12.new(self)
259
+ else
260
+ Arel::Visitors::Oracle.new(self)
261
+ end
262
+ end
263
+
264
+ def return_value_after_insert?(column) # :nodoc:
265
+ # TODO: Return true if there this column will be populated (e.g by a sequence)
266
+ super
267
+ end
268
+
269
+ def build_statement_pool
270
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
271
+ end
272
+
273
+ def supports_savepoints? # :nodoc:
274
+ true
275
+ end
276
+
277
+ def supports_transaction_isolation? # :nodoc:
278
+ true
279
+ end
280
+
281
+ def supports_foreign_keys?
282
+ true
283
+ end
284
+
285
+ def supports_optimizer_hints?
286
+ true
287
+ end
288
+
289
+ def supports_common_table_expressions?
290
+ true
291
+ end
292
+
293
+ def supports_views?
294
+ true
295
+ end
296
+
297
+ def supports_fetch_first_n_rows_and_offset?
298
+ false
299
+
300
+ # TODO: At this point the connection is not initialized yet,
301
+ # so `database_version` raises an error
302
+ #
303
+ # !use_old_oracle_visitor && database_version.first >= 12
304
+ end
305
+
306
+ def supports_datetime_with_precision?
307
+ true
308
+ end
309
+
310
+ def supports_comments?
311
+ true
312
+ end
313
+
314
+ def supports_multi_insert?
315
+ database_version.to_s >= [11, 2].to_s
316
+ end
317
+
318
+ def supports_virtual_columns?
319
+ database_version.first >= 11
320
+ end
321
+
322
+ def supports_json?
323
+ # Oracle Database 12.1 or higher version supports JSON.
324
+ # However, Oracle enhanced adapter has limited support for JSON data type.
325
+ # which does not pass many of ActiveRecord JSON tests.
326
+ #
327
+ # No migration supported for :json type due to there is no `JSON` data type
328
+ # in Oracle Database itself.
329
+ #
330
+ # If you want to use JSON data type, here are steps
331
+ # 1.Define :string or :text in migration
332
+ #
333
+ # create_table :test_posts, force: true do |t|
334
+ # t.string :title
335
+ # t.text :article
336
+ # end
337
+ #
338
+ # 2. Set :json attributes
339
+ #
340
+ # class TestPost < ActiveRecord::Base
341
+ # attribute :title, :json
342
+ # attribute :article, :json
343
+ # end
344
+ #
345
+ # 3. Add `is json` database constraints by running sql statements
346
+ #
347
+ # alter table test_posts add constraint test_posts_title_is_json check (title is json)
348
+ # alter table test_posts add constraint test_posts_article_is_json check (article is json)
349
+ #
350
+ false
351
+ end
352
+
353
+ def supports_longer_identifier?
354
+ if !use_shorter_identifier && database_version.to_s >= [12, 2].to_s
355
+ true
356
+ else
357
+ false
358
+ end
359
+ end
360
+
361
+ # :stopdoc:
362
+ DEFAULT_NLS_PARAMETERS = {
363
+ nls_calendar: nil,
364
+ nls_comp: nil,
365
+ nls_currency: nil,
366
+ nls_date_language: nil,
367
+ nls_dual_currency: nil,
368
+ nls_iso_currency: nil,
369
+ nls_language: nil,
370
+ nls_length_semantics: "CHAR",
371
+ nls_nchar_conv_excp: nil,
372
+ nls_numeric_characters: nil,
373
+ nls_sort: nil,
374
+ nls_territory: nil,
375
+ nls_timestamp_tz_format: nil,
376
+ nls_time_format: nil,
377
+ nls_time_tz_format: nil
378
+ }
379
+
380
+ # :stopdoc:
381
+ FIXED_NLS_PARAMETERS = {
382
+ nls_date_format: "YYYY-MM-DD HH24:MI:SS",
383
+ nls_timestamp_format: "YYYY-MM-DD HH24:MI:SS:FF6"
384
+ }
385
+
386
+ # :stopdoc:
387
+ NATIVE_DATABASE_TYPES = {
388
+ primary_key: "NUMBER(38) NOT NULL PRIMARY KEY",
389
+ string: { name: "VARCHAR2", limit: 255 },
390
+ text: { name: "CLOB" },
391
+ ntext: { name: "NCLOB" },
392
+ integer: { name: "NUMBER", limit: 38 },
393
+ float: { name: "BINARY_FLOAT" },
394
+ decimal: { name: "NUMBER" },
395
+ datetime: { name: "TIMESTAMP" },
396
+ timestamp: { name: "TIMESTAMP" },
397
+ timestamptz: { name: "TIMESTAMP WITH TIME ZONE" },
398
+ timestampltz: { name: "TIMESTAMP WITH LOCAL TIME ZONE" },
399
+ time: { name: "TIMESTAMP" },
400
+ date: { name: "DATE" },
401
+ binary: { name: "BLOB" },
402
+ boolean: { name: "NUMBER", limit: 1 },
403
+ raw: { name: "RAW", limit: 2000 },
404
+ bigint: { name: "NUMBER", limit: 19 }
405
+ }
406
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
407
+ NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
408
+ boolean: { name: "VARCHAR2", limit: 1 }
409
+ )
410
+ # :startdoc:
411
+
412
+ def native_database_types # :nodoc:
413
+ emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
414
+ end
415
+
416
+ # CONNECTION MANAGEMENT ====================================
417
+ #
418
+
419
+ # If SQL statement fails due to lost connection then reconnect
420
+ # and retry SQL statement if autocommit mode is enabled.
421
+ # By default this functionality is disabled.
422
+ attr_reader :auto_retry # :nodoc:
423
+ @auto_retry = false
424
+
425
+ def auto_retry=(value) # :nodoc:
426
+ @auto_retry = value
427
+ _connection.auto_retry = value if _connection
428
+ end
429
+
430
+ # return raw OCI8 or JDBC connection
431
+ def raw_connection
432
+ verify!
433
+ _connection.raw_connection
434
+ end
435
+
436
+ # Returns true if the connection is active.
437
+ def active? # :nodoc:
438
+ # Pings the connection to check if it's still good. Note that an
439
+ # #active? method is also available, but that simply returns the
440
+ # last known state, which isn't good enough if the connection has
441
+ # gone stale since the last use.
442
+ _connection.ping
443
+ rescue OracleEnhanced::ConnectionException
444
+ false
445
+ end
446
+
447
+ def reconnect
448
+ _connection.reset # tentative
449
+ rescue OracleEnhanced::ConnectionException
450
+ connect
451
+ end
452
+
453
+ # Reconnects to the database.
454
+ def reconnect!(restore_transactions: false) # :nodoc:
455
+ super
456
+ _connection.reset!
457
+ rescue OracleEnhanced::ConnectionException => e
458
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
459
+ end
460
+
461
+ def clear_cache!(*args, **kwargs)
462
+ super
463
+ self.class.clear_type_map!
464
+ end
465
+
466
+ def reset!
467
+ clear_cache!
468
+ super
469
+ end
470
+
471
+ # Disconnects from the database.
472
+ def disconnect! # :nodoc:
473
+ super
474
+ _connection.logoff rescue nil
475
+ end
476
+
477
+ def discard!
478
+ super
479
+ _connection = nil
480
+ end
481
+
482
+ # use in set_sequence_name to avoid fetching primary key value from sequence
483
+ AUTOGENERATED_SEQUENCE_NAME = "autogenerated"
484
+
485
+ # Returns the next sequence value from a sequence generator. Not generally
486
+ # called directly; used by ActiveRecord to get the next primary key value
487
+ # when inserting a new database record (see #prefetch_primary_key?).
488
+ def next_sequence_value(sequence_name)
489
+ # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
490
+ raise ArgumentError.new "Trigger based primary key is not supported" if sequence_name == AUTOGENERATED_SEQUENCE_NAME
491
+ # call directly connection method to avoid prepared statement which causes fetching of next sequence value twice
492
+ select_value(<<~SQL.squish, "SCHEMA")
493
+ SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual
494
+ SQL
495
+ end
496
+
497
+ # Returns true for Oracle adapter (since Oracle requires primary key
498
+ # values to be pre-fetched before insert). See also #next_sequence_value.
499
+ def prefetch_primary_key?(table_name = nil)
500
+ return true if table_name.nil?
501
+ table_name = table_name.to_s
502
+ do_not_prefetch = @do_not_prefetch_primary_key[table_name]
503
+ if do_not_prefetch.nil?
504
+ owner, desc_table_name = _connection.describe(table_name)
505
+ @do_not_prefetch_primary_key[table_name] = do_not_prefetch = !has_primary_key?(table_name, owner, desc_table_name)
506
+ end
507
+ !do_not_prefetch
508
+ end
509
+
510
+ def reset_pk_sequence!(table_name, primary_key = nil, sequence_name = nil) # :nodoc:
511
+ return nil unless data_source_exists?(table_name)
512
+ unless primary_key && sequence_name
513
+ # *Note*: Only primary key is implemented - sequence will be nil.
514
+ primary_key, sequence_name = pk_and_sequence_for(table_name)
515
+ # TODO This sequence_name implemantation is just enough
516
+ # to satisty fixures. To get correct sequence_name always
517
+ # pk_and_sequence_for method needs some work.
518
+ begin
519
+ sequence_name = table_name.classify.constantize.sequence_name
520
+ rescue
521
+ sequence_name = default_sequence_name(table_name)
522
+ end
523
+ end
524
+
525
+ if @logger && primary_key && !sequence_name
526
+ @logger.warn "#{table_name} has primary key #{primary_key} with no default sequence"
527
+ end
528
+
529
+ if primary_key && sequence_name
530
+ new_start_value = select_value(<<~SQL.squish, "SCHEMA")
531
+ select NVL(max(#{quote_column_name(primary_key)}),0) + 1 from #{quote_table_name(table_name)}
532
+ SQL
533
+
534
+ execute "DROP SEQUENCE #{quote_table_name(sequence_name)}"
535
+ execute "CREATE SEQUENCE #{quote_table_name(sequence_name)} START WITH #{new_start_value}"
536
+ end
537
+ end
538
+
539
+ # Current database name
540
+ def current_database
541
+ select_value(<<~SQL.squish, "SCHEMA")
542
+ SELECT SYS_CONTEXT('userenv', 'con_name') FROM dual
543
+ SQL
544
+ rescue ActiveRecord::StatementInvalid
545
+ select_value(<<~SQL.squish, "SCHEMA")
546
+ SELECT SYS_CONTEXT('userenv', 'db_name') FROM dual
547
+ SQL
548
+ end
549
+
550
+ # Current database session user
551
+ def current_user
552
+ select_value(<<~SQL.squish, "SCHEMA")
553
+ SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual
554
+ SQL
555
+ end
556
+
557
+ # Current database session schema
558
+ def current_schema
559
+ select_value(<<~SQL.squish, "SCHEMA")
560
+ SELECT SYS_CONTEXT('userenv', 'current_schema') FROM dual
561
+ SQL
562
+ end
563
+
564
+ # Default tablespace name of current user
565
+ def default_tablespace
566
+ select_value(<<~SQL.squish, "SCHEMA")
567
+ SELECT LOWER(default_tablespace) FROM user_users
568
+ WHERE username = SYS_CONTEXT('userenv', 'current_schema')
569
+ SQL
570
+ end
571
+
572
+ def column_definitions(table_name)
573
+ (owner, desc_table_name) = _connection.describe(table_name)
574
+
575
+ select_all(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("table_name", desc_table_name)])
576
+ SELECT cols.column_name AS name, cols.data_type AS sql_type,
577
+ cols.data_default, cols.nullable, cols.virtual_column, cols.hidden_column,
578
+ cols.data_type_owner AS sql_type_owner,
579
+ DECODE(cols.data_type, 'NUMBER', data_precision,
580
+ 'FLOAT', data_precision,
581
+ 'VARCHAR2', DECODE(char_used, 'C', char_length, data_length),
582
+ 'RAW', DECODE(char_used, 'C', char_length, data_length),
583
+ 'CHAR', DECODE(char_used, 'C', char_length, data_length),
584
+ NULL) AS limit,
585
+ DECODE(data_type, 'NUMBER', data_scale, NULL) AS scale,
586
+ comments.comments as column_comment
587
+ FROM all_tab_cols cols, all_col_comments comments
588
+ WHERE cols.owner = :owner
589
+ AND cols.table_name = :table_name
590
+ AND cols.hidden_column = 'NO'
591
+ AND cols.owner = comments.owner
592
+ AND cols.table_name = comments.table_name
593
+ AND cols.column_name = comments.column_name
594
+ ORDER BY cols.column_id
595
+ SQL
596
+ end
597
+
598
+ def clear_table_columns_cache(table_name)
599
+ @columns_cache[table_name.to_s] = nil
600
+ end
601
+
602
+ # Find a table's primary key and sequence.
603
+ # *Note*: Only primary key is implemented - sequence will be nil.
604
+ def pk_and_sequence_for(table_name, owner = nil, desc_table_name = nil) # :nodoc:
605
+ (owner, desc_table_name) = _connection.describe(table_name)
606
+
607
+ seqs = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("sequence_name", default_sequence_name(desc_table_name))])
608
+ select us.sequence_name
609
+ from all_sequences us
610
+ where us.sequence_owner = :owner
611
+ and us.sequence_name = upper(:sequence_name)
612
+ SQL
613
+
614
+ # changed back from user_constraints to all_constraints for consistency
615
+ pks = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("table_name", desc_table_name)])
616
+ SELECT cc.column_name
617
+ FROM all_constraints c, all_cons_columns cc
618
+ WHERE c.owner = :owner
619
+ AND c.table_name = :table_name
620
+ AND c.constraint_type = 'P'
621
+ AND cc.owner = c.owner
622
+ AND cc.constraint_name = c.constraint_name
623
+ SQL
624
+
625
+ warn <<~WARNING if pks.count > 1
626
+ WARNING: Active Record does not support composite primary key.
627
+
628
+ #{table_name} has composite primary key. Composite primary key is ignored.
629
+ WARNING
630
+
631
+ # only support single column keys
632
+ pks.size == 1 ? [oracle_downcase(pks.first),
633
+ oracle_downcase(seqs.first)] : nil
634
+ end
635
+
636
+ # Returns just a table's primary key
637
+ def primary_key(table_name)
638
+ pk_and_sequence = pk_and_sequence_for(table_name)
639
+ pk_and_sequence && pk_and_sequence.first
640
+ end
641
+
642
+ def has_primary_key?(table_name, owner = nil, desc_table_name = nil) # :nodoc:
643
+ !pk_and_sequence_for(table_name, owner, desc_table_name).nil?
644
+ end
645
+
646
+ def primary_keys(table_name) # :nodoc:
647
+ (_owner, desc_table_name) = _connection.describe(table_name)
648
+
649
+ pks = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("table_name", desc_table_name)])
650
+ SELECT cc.column_name
651
+ FROM all_constraints c, all_cons_columns cc
652
+ WHERE c.owner = SYS_CONTEXT('userenv', 'current_schema')
653
+ AND c.table_name = :table_name
654
+ AND c.constraint_type = 'P'
655
+ AND cc.owner = c.owner
656
+ AND cc.constraint_name = c.constraint_name
657
+ order by cc.position
658
+ SQL
659
+ pks.map { |pk| oracle_downcase(pk) }
660
+ end
661
+
662
+ def columns_for_distinct(columns, orders) # :nodoc:
663
+ # construct a valid columns name for DISTINCT clause,
664
+ # ie. one that includes the ORDER BY columns, using FIRST_VALUE such that
665
+ # the inclusion of these columns doesn't invalidate the DISTINCT
666
+ #
667
+ # It does not construct DISTINCT clause. Just return column names for distinct.
668
+ order_columns = orders.reject(&:blank?).map { |s|
669
+ s = visitor.compile(s) unless s.is_a?(String)
670
+ # remove any ASC/DESC modifiers
671
+ s.gsub(/\s+(ASC|DESC)\s*?/i, "")
672
+ }.reject(&:blank?).map.with_index { |column, i|
673
+ "FIRST_VALUE(#{column}) OVER (PARTITION BY #{columns} ORDER BY #{column}) AS alias_#{i}__"
674
+ }
675
+ (order_columns << super).join(", ")
676
+ end
677
+
678
+ def temporary_table?(table_name) # :nodoc:
679
+ select_value_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("table_name", table_name.upcase)]) == "Y"
680
+ SELECT
681
+ temporary FROM all_tables WHERE table_name = :table_name and owner = SYS_CONTEXT('userenv', 'current_schema')
682
+ SQL
683
+ end
684
+
685
+ def max_identifier_length
686
+ supports_longer_identifier? ? 128 : 30
687
+ end
688
+ alias table_alias_length max_identifier_length
689
+ alias index_name_length max_identifier_length
690
+
691
+ # This is to ensure rails is not shortening the index name,
692
+ # in order to preserve the local shortening behavior.
693
+ def max_index_name_size
694
+ 128
695
+ end
696
+
697
+ def get_database_version
698
+ _connection.database_version
699
+ end
700
+
701
+ def check_version
702
+ version = get_database_version.join(".").to_f
703
+
704
+ if version < 10
705
+ raise "Your version of Oracle (#{version}) is too old. Active Record Oracle enhanced adapter supports Oracle >= 10g."
706
+ end
707
+ end
708
+
709
+ private def _connection
710
+ @unconfigured_connection || @raw_connection
711
+ end
712
+
713
+ class << self
714
+ def type_map
715
+ @type_map ||= Type::TypeMap.new.tap { |m| initialize_type_map(m) }
716
+ @type_map
717
+ end
718
+
719
+ def clear_type_map!
720
+ @type_map = nil
721
+ end
722
+
723
+ private
724
+ def initialize_type_map(m)
725
+ super
726
+ # oracle
727
+ register_class_with_precision m, %r(WITH TIME ZONE)i, Type::OracleEnhanced::TimestampTz
728
+ register_class_with_precision m, %r(WITH LOCAL TIME ZONE)i, Type::OracleEnhanced::TimestampLtz
729
+ register_class_with_limit m, %r(raw)i, Type::OracleEnhanced::Raw
730
+ register_class_with_limit m, %r{^(char)}i, Type::OracleEnhanced::CharacterString
731
+ register_class_with_limit m, %r{^(nchar)}i, Type::OracleEnhanced::String
732
+ register_class_with_limit m, %r(varchar)i, Type::OracleEnhanced::String
733
+ register_class_with_limit m, %r(clob)i, Type::OracleEnhanced::Text
734
+ register_class_with_limit m, %r(nclob)i, Type::OracleEnhanced::NationalCharacterText
735
+
736
+ m.register_type "NCHAR", Type::OracleEnhanced::NationalCharacterString.new
737
+ m.alias_type %r(NVARCHAR2)i, "NCHAR"
738
+
739
+ m.register_type(%r(NUMBER)i) do |sql_type|
740
+ scale = extract_scale(sql_type)
741
+ precision = extract_precision(sql_type)
742
+ limit = extract_limit(sql_type)
743
+ if scale == 0
744
+ Type::OracleEnhanced::Integer.new(precision: precision, limit: limit)
745
+ else
746
+ Type::Decimal.new(precision: precision, scale: scale)
747
+ end
748
+ end
749
+
750
+ if OracleEnhancedAdapter.emulate_booleans
751
+ m.register_type %r(^NUMBER\(1\))i, Type::Boolean.new
752
+ end
753
+ end
754
+ end
755
+
756
+ def type_map
757
+ self.class.type_map
758
+ end
759
+
760
+ def extract_value_from_default(default)
761
+ case default
762
+ when String
763
+ default.gsub("''", "'")
764
+ else
765
+ default
766
+ end
767
+ end
768
+
769
+ def extract_limit(sql_type) # :nodoc:
770
+ case sql_type
771
+ when /^bigint/i
772
+ 19
773
+ when /\((.*)\)/
774
+ $1.to_i
775
+ end
776
+ end
777
+
778
+ def translate_exception(exception, message:, sql:, binds:) # :nodoc:
779
+ case _connection.error_code(exception)
780
+ when 1
781
+ RecordNotUnique.new(message, sql: sql, binds: binds)
782
+ when 60
783
+ Deadlocked.new(message)
784
+ when 900, 904, 942, 955, 1418, 2289, 2449, 17008
785
+ ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
786
+ when 1400
787
+ ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
788
+ when 2291, 2292
789
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
790
+ when 12899
791
+ ValueTooLong.new(message, sql: sql, binds: binds)
792
+ else
793
+ super
794
+ end
795
+ end
796
+
797
+ # create bind object for type String
798
+ def bind_string(name, value)
799
+ ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new)
800
+ end
801
+
802
+ # call select_values using binds even if surrounding SQL preparation/execution is done + # with conn.unprepared_statement (like AR.to_sql)
803
+ def select_values_forcing_binds(arel, name, binds)
804
+ # remove possible force of unprepared SQL during dictionary access
805
+ unprepared_statement_forced = prepared_statements_disabled_cache.include?(object_id)
806
+ prepared_statements_disabled_cache.delete(object_id) if unprepared_statement_forced
807
+
808
+ select_values(arel, name, binds)
809
+ ensure
810
+ # Restore unprepared_statement setting for surrounding SQL
811
+ prepared_statements_disabled_cache.add(object_id) if unprepared_statement_forced
812
+ end
813
+
814
+ def select_value_forcing_binds(arel, name, binds)
815
+ single_value_from_rows(select_values_forcing_binds(arel, name, binds))
816
+ end
817
+
818
+ ActiveRecord::Type.register(:boolean, Type::OracleEnhanced::Boolean, adapter: :oracle_enhanced)
819
+ ActiveRecord::Type.register(:json, Type::OracleEnhanced::Json, adapter: :oracle_enhanced)
820
+ end
821
+ end
822
+ end
823
+
824
+ ## Register OracleEnhancedAdapter as the adapter to use for "oracle_enhanced" connection string
825
+ if ActiveRecord::ConnectionAdapters.respond_to?(:register)
826
+ ActiveRecord::ConnectionAdapters.register(
827
+ "oracle_enhanced",
828
+ "ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter",
829
+ "active_record/connection_adapters/oracle_enhanced_adapter"
830
+ )
831
+
832
+ # This is similar to the notion of emulating the original OracleAdapter but
833
+ # using the OracleEnhancedAdapter instead, but without using the emulate flag.
834
+ # Instead this will get picked up if you set the adapter to 'oracle' in the database config.
835
+ #
836
+ # Register OracleAdapter as the adapter to use for "oracle" connection string
837
+ ActiveRecord::ConnectionAdapters.register(
838
+ "oracle",
839
+ "ActiveRecord::ConnectionAdapters::OracleAdapter",
840
+ "active_record/connection_adapters/emulation/oracle_adapter"
841
+ )
842
+ end
843
+
844
+ require "active_record/connection_adapters/oracle_enhanced/version"
845
+
846
+ module ActiveRecord
847
+ autoload :OracleEnhancedProcedures, "active_record/connection_adapters/oracle_enhanced/procedures"
848
+ end
849
+
850
+ # Workaround for https://github.com/jruby/jruby/issues/6267
851
+ # JRuby implements ObjectSpace::WeakMap#values natively since 9.4, where the
852
+ # internal `map` field this workaround reads does not exist anymore.
853
+ if RUBY_ENGINE == "jruby" && !ObjectSpace::WeakMap.method_defined?(:values)
854
+ require "jruby"
855
+
856
+ class org.jruby::RubyObjectSpace::WeakMap
857
+ field_reader :map
858
+ end
859
+
860
+ class ObjectSpace::WeakMap
861
+ def values
862
+ JRuby.ref(self).map.values.reject(&:nil?)
863
+ end
864
+ end
865
+ end