activerecord-oracle_enhanced-adapter 8.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1971 -0
  3. data/License.txt +20 -0
  4. data/README.md +947 -0
  5. data/VERSION +1 -0
  6. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +7 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +24 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +137 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +359 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +47 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +325 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +63 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +71 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +629 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +57 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +465 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +195 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +186 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +99 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +197 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +739 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +394 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +34 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +886 -0
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +19 -0
  30. data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
  31. data/lib/active_record/type/oracle_enhanced/integer.rb +14 -0
  32. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  34. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  35. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  36. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  37. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  38. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  39. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  40. data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
  41. data/lib/arel/visitors/oracle.rb +216 -0
  42. data/lib/arel/visitors/oracle12.rb +121 -0
  43. data/lib/arel/visitors/oracle_common.rb +51 -0
  44. data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +24 -0
  45. data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +40 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/composite_spec.rb +84 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +589 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced/dbconsole_spec.rb +63 -0
  51. data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
  52. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
  53. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
  54. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  55. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1318 -0
  56. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +485 -0
  57. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
  58. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
  59. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  60. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
  61. data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
  62. data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
  63. data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
  64. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
  65. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  66. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +101 -0
  67. data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
  68. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
  69. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  70. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
  71. data/spec/active_record/oracle_enhanced/type/text_spec.rb +295 -0
  72. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +107 -0
  73. data/spec/spec_config.yaml.template +11 -0
  74. data/spec/spec_helper.rb +225 -0
  75. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  76. data/spec/support/alter_system_user_password.sql +2 -0
  77. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  78. metadata +181 -0
@@ -0,0 +1,886 @@
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
+ # Opens a database console session via sqlplus.
270
+ #
271
+ # Called by Rails' `bin/rails dbconsole` command on the adapter class
272
+ # returned from adapter registration. Builds an Oracle logon string of
273
+ # the form `user[/password]@database` and execs `sqlplus`.
274
+ def self.dbconsole(config, options = {})
275
+ oracle_config = config.configuration_hash
276
+ logon = +""
277
+
278
+ if oracle_config[:username]
279
+ logon << oracle_config[:username]
280
+ logon << "/#{oracle_config[:password]}" if oracle_config[:password] && options[:include_password]
281
+ logon << "@#{config.database}" if config.database
282
+ end
283
+
284
+ find_cmd_and_exec(ActiveRecord.database_cli[:oracle] || "sqlplus", logon)
285
+ end
286
+
287
+ def build_statement_pool
288
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
289
+ end
290
+
291
+ def supports_savepoints? # :nodoc:
292
+ true
293
+ end
294
+
295
+ def supports_transaction_isolation? # :nodoc:
296
+ true
297
+ end
298
+
299
+ def supports_foreign_keys?
300
+ true
301
+ end
302
+
303
+ def supports_optimizer_hints?
304
+ true
305
+ end
306
+
307
+ def supports_common_table_expressions?
308
+ true
309
+ end
310
+
311
+ def supports_views?
312
+ true
313
+ end
314
+
315
+ def supports_fetch_first_n_rows_and_offset?
316
+ false
317
+
318
+ # TODO: At this point the connection is not initialized yet,
319
+ # so `database_version` raises an error
320
+ #
321
+ # !use_old_oracle_visitor && database_version.first >= 12
322
+ end
323
+
324
+ def supports_datetime_with_precision?
325
+ true
326
+ end
327
+
328
+ def supports_comments?
329
+ true
330
+ end
331
+
332
+ def supports_multi_insert?
333
+ database_version.to_s >= [11, 2].to_s
334
+ end
335
+
336
+ def supports_virtual_columns?
337
+ database_version.first >= 11
338
+ end
339
+
340
+ def supports_json?
341
+ # Oracle Database 12.1 or higher version supports JSON.
342
+ # However, Oracle enhanced adapter has limited support for JSON data type.
343
+ # which does not pass many of ActiveRecord JSON tests.
344
+ #
345
+ # No migration supported for :json type due to there is no `JSON` data type
346
+ # in Oracle Database itself.
347
+ #
348
+ # If you want to use JSON data type, here are steps
349
+ # 1.Define :string or :text in migration
350
+ #
351
+ # create_table :test_posts, force: true do |t|
352
+ # t.string :title
353
+ # t.text :article
354
+ # end
355
+ #
356
+ # 2. Set :json attributes
357
+ #
358
+ # class TestPost < ActiveRecord::Base
359
+ # attribute :title, :json
360
+ # attribute :article, :json
361
+ # end
362
+ #
363
+ # 3. Add `is json` database constraints by running sql statements
364
+ #
365
+ # alter table test_posts add constraint test_posts_title_is_json check (title is json)
366
+ # alter table test_posts add constraint test_posts_article_is_json check (article is json)
367
+ #
368
+ false
369
+ end
370
+
371
+ def supports_longer_identifier?
372
+ if !use_shorter_identifier && database_version.to_s >= [12, 2].to_s
373
+ true
374
+ else
375
+ false
376
+ end
377
+ end
378
+
379
+ # :stopdoc:
380
+ DEFAULT_NLS_PARAMETERS = {
381
+ nls_calendar: nil,
382
+ nls_comp: nil,
383
+ nls_currency: nil,
384
+ nls_date_language: nil,
385
+ nls_dual_currency: nil,
386
+ nls_iso_currency: nil,
387
+ nls_language: nil,
388
+ nls_length_semantics: "CHAR",
389
+ nls_nchar_conv_excp: nil,
390
+ nls_numeric_characters: nil,
391
+ nls_sort: nil,
392
+ nls_territory: nil,
393
+ nls_timestamp_tz_format: nil,
394
+ nls_time_format: nil,
395
+ nls_time_tz_format: nil
396
+ }
397
+
398
+ # :stopdoc:
399
+ FIXED_NLS_PARAMETERS = {
400
+ nls_date_format: "YYYY-MM-DD HH24:MI:SS",
401
+ nls_timestamp_format: "YYYY-MM-DD HH24:MI:SS:FF6"
402
+ }
403
+
404
+ # :stopdoc:
405
+ NATIVE_DATABASE_TYPES = {
406
+ primary_key: "NUMBER(38) NOT NULL PRIMARY KEY",
407
+ string: { name: "VARCHAR2", limit: 255 },
408
+ text: { name: "CLOB" },
409
+ ntext: { name: "NCLOB" },
410
+ integer: { name: "NUMBER", limit: 38 },
411
+ float: { name: "BINARY_FLOAT" },
412
+ decimal: { name: "NUMBER" },
413
+ datetime: { name: "TIMESTAMP" },
414
+ timestamp: { name: "TIMESTAMP" },
415
+ timestamptz: { name: "TIMESTAMP WITH TIME ZONE" },
416
+ timestampltz: { name: "TIMESTAMP WITH LOCAL TIME ZONE" },
417
+ time: { name: "TIMESTAMP" },
418
+ date: { name: "DATE" },
419
+ binary: { name: "BLOB" },
420
+ boolean: { name: "NUMBER", limit: 1 },
421
+ raw: { name: "RAW", limit: 2000 },
422
+ bigint: { name: "NUMBER", limit: 19 }
423
+ }
424
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
425
+ NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
426
+ boolean: { name: "VARCHAR2", limit: 1 }
427
+ )
428
+ # :startdoc:
429
+
430
+ def native_database_types # :nodoc:
431
+ self.class.native_database_types
432
+ end
433
+
434
+ # CONNECTION MANAGEMENT ====================================
435
+ #
436
+
437
+ # If SQL statement fails due to lost connection then reconnect
438
+ # and retry SQL statement if autocommit mode is enabled.
439
+ # By default this functionality is disabled.
440
+ attr_reader :auto_retry # :nodoc:
441
+ @auto_retry = false
442
+
443
+ def auto_retry=(value) # :nodoc:
444
+ @auto_retry = value
445
+ _connection.auto_retry = value if _connection
446
+ end
447
+
448
+ # return raw OCI8 or JDBC connection
449
+ def raw_connection
450
+ verify!
451
+ _connection.raw_connection
452
+ end
453
+
454
+ # Returns true if the connection is active.
455
+ def active? # :nodoc:
456
+ # Pings the connection to check if it's still good. Note that an
457
+ # #active? method is also available, but that simply returns the
458
+ # last known state, which isn't good enough if the connection has
459
+ # gone stale since the last use.
460
+ _connection.ping
461
+ rescue OracleEnhanced::ConnectionException
462
+ false
463
+ end
464
+
465
+ def reconnect
466
+ _connection.reset # tentative
467
+ rescue OracleEnhanced::ConnectionException
468
+ connect
469
+ end
470
+
471
+ # Reconnects to the database.
472
+ def reconnect!(restore_transactions: false) # :nodoc:
473
+ super
474
+ _connection.reset!
475
+ rescue OracleEnhanced::ConnectionException => e
476
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
477
+ end
478
+
479
+ def clear_cache!(*args, **kwargs)
480
+ super
481
+ self.class.clear_type_map!
482
+ end
483
+
484
+ def reset!
485
+ clear_cache!
486
+ super
487
+ end
488
+
489
+ # Disconnects from the database.
490
+ def disconnect! # :nodoc:
491
+ super
492
+ _connection.logoff rescue nil
493
+ end
494
+
495
+ def discard!
496
+ super
497
+ _connection = nil
498
+ end
499
+
500
+ # use in set_sequence_name to avoid fetching primary key value from sequence
501
+ AUTOGENERATED_SEQUENCE_NAME = "autogenerated"
502
+
503
+ # Returns the next sequence value from a sequence generator. Not generally
504
+ # called directly; used by ActiveRecord to get the next primary key value
505
+ # when inserting a new database record (see #prefetch_primary_key?).
506
+ def next_sequence_value(sequence_name)
507
+ # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
508
+ raise ArgumentError.new "Trigger based primary key is not supported" if sequence_name == AUTOGENERATED_SEQUENCE_NAME
509
+ # call directly connection method to avoid prepared statement which causes fetching of next sequence value twice
510
+ select_value(<<~SQL.squish, "SCHEMA")
511
+ SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual
512
+ SQL
513
+ end
514
+
515
+ # Returns true for Oracle adapter (since Oracle requires primary key
516
+ # values to be pre-fetched before insert). See also #next_sequence_value.
517
+ def prefetch_primary_key?(table_name = nil)
518
+ return true if table_name.nil?
519
+ table_name = table_name.to_s
520
+ do_not_prefetch = @do_not_prefetch_primary_key[table_name]
521
+ if do_not_prefetch.nil?
522
+ owner, desc_table_name = _connection.describe(table_name)
523
+ @do_not_prefetch_primary_key[table_name] = do_not_prefetch = !has_primary_key?(table_name, owner, desc_table_name)
524
+ end
525
+ !do_not_prefetch
526
+ end
527
+
528
+ def reset_pk_sequence!(table_name, primary_key = nil, sequence_name = nil) # :nodoc:
529
+ return nil unless data_source_exists?(table_name)
530
+ unless primary_key && sequence_name
531
+ # *Note*: Only primary key is implemented - sequence will be nil.
532
+ primary_key, sequence_name = pk_and_sequence_for(table_name)
533
+ # TODO This sequence_name implemantation is just enough
534
+ # to satisty fixures. To get correct sequence_name always
535
+ # pk_and_sequence_for method needs some work.
536
+ begin
537
+ sequence_name = table_name.classify.constantize.sequence_name
538
+ rescue
539
+ sequence_name = default_sequence_name(table_name)
540
+ end
541
+ end
542
+
543
+ if @logger && primary_key && !sequence_name
544
+ @logger.warn "#{table_name} has primary key #{primary_key} with no default sequence"
545
+ end
546
+
547
+ if primary_key && sequence_name
548
+ new_start_value = select_value(<<~SQL.squish, "SCHEMA")
549
+ select NVL(max(#{quote_column_name(primary_key)}),0) + 1 from #{quote_table_name(table_name)}
550
+ SQL
551
+
552
+ execute "DROP SEQUENCE #{quote_table_name(sequence_name)}"
553
+ execute "CREATE SEQUENCE #{quote_table_name(sequence_name)} START WITH #{new_start_value}"
554
+ end
555
+ end
556
+
557
+ # Current database name
558
+ def current_database
559
+ select_value(<<~SQL.squish, "SCHEMA")
560
+ SELECT SYS_CONTEXT('userenv', 'con_name') FROM dual
561
+ SQL
562
+ rescue ActiveRecord::StatementInvalid
563
+ select_value(<<~SQL.squish, "SCHEMA")
564
+ SELECT SYS_CONTEXT('userenv', 'db_name') FROM dual
565
+ SQL
566
+ end
567
+
568
+ # Current database session user
569
+ def current_user
570
+ select_value(<<~SQL.squish, "SCHEMA")
571
+ SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual
572
+ SQL
573
+ end
574
+
575
+ # Current database session schema
576
+ def current_schema
577
+ select_value(<<~SQL.squish, "SCHEMA")
578
+ SELECT SYS_CONTEXT('userenv', 'current_schema') FROM dual
579
+ SQL
580
+ end
581
+
582
+ # Default tablespace name of current user
583
+ def default_tablespace
584
+ select_value(<<~SQL.squish, "SCHEMA")
585
+ SELECT LOWER(default_tablespace) FROM user_users
586
+ WHERE username = SYS_CONTEXT('userenv', 'current_schema')
587
+ SQL
588
+ end
589
+
590
+ def column_definitions(table_name)
591
+ (owner, desc_table_name) = _connection.describe(table_name)
592
+
593
+ select_all(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("table_name", desc_table_name)])
594
+ SELECT cols.column_name AS name, cols.data_type AS sql_type,
595
+ cols.data_default, cols.nullable, cols.virtual_column, cols.hidden_column,
596
+ cols.data_type_owner AS sql_type_owner,
597
+ DECODE(cols.data_type, 'NUMBER', data_precision,
598
+ 'FLOAT', data_precision,
599
+ 'VARCHAR2', DECODE(char_used, 'C', char_length, data_length),
600
+ 'RAW', DECODE(char_used, 'C', char_length, data_length),
601
+ 'CHAR', DECODE(char_used, 'C', char_length, data_length),
602
+ NULL) AS limit,
603
+ DECODE(data_type, 'NUMBER', data_scale, NULL) AS scale,
604
+ comments.comments as column_comment
605
+ FROM all_tab_cols cols, all_col_comments comments
606
+ WHERE cols.owner = :owner
607
+ AND cols.table_name = :table_name
608
+ AND cols.hidden_column = 'NO'
609
+ AND cols.owner = comments.owner
610
+ AND cols.table_name = comments.table_name
611
+ AND cols.column_name = comments.column_name
612
+ ORDER BY cols.column_id
613
+ SQL
614
+ end
615
+
616
+ def clear_table_columns_cache(table_name)
617
+ @columns_cache[table_name.to_s] = nil
618
+ end
619
+
620
+ # Find a table's primary key and sequence.
621
+ # *Note*: Only primary key is implemented - sequence will be nil.
622
+ def pk_and_sequence_for(table_name, owner = nil, desc_table_name = nil) # :nodoc:
623
+ (owner, desc_table_name) = _connection.describe(table_name)
624
+
625
+ seqs = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("sequence_name", default_sequence_name(desc_table_name))])
626
+ select us.sequence_name
627
+ from all_sequences us
628
+ where us.sequence_owner = :owner
629
+ and us.sequence_name = upper(:sequence_name)
630
+ SQL
631
+
632
+ # changed back from user_constraints to all_constraints for consistency
633
+ pks = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("table_name", desc_table_name)])
634
+ SELECT cc.column_name
635
+ FROM all_constraints c, all_cons_columns cc
636
+ WHERE c.owner = :owner
637
+ AND c.table_name = :table_name
638
+ AND c.constraint_type = 'P'
639
+ AND cc.owner = c.owner
640
+ AND cc.constraint_name = c.constraint_name
641
+ SQL
642
+
643
+ warn <<~WARNING if pks.count > 1
644
+ WARNING: Active Record does not support composite primary key.
645
+
646
+ #{table_name} has composite primary key. Composite primary key is ignored.
647
+ WARNING
648
+
649
+ # only support single column keys
650
+ pks.size == 1 ? [oracle_downcase(pks.first),
651
+ oracle_downcase(seqs.first)] : nil
652
+ end
653
+
654
+ # Returns just a table's primary key
655
+ def primary_key(table_name)
656
+ pk_and_sequence = pk_and_sequence_for(table_name)
657
+ pk_and_sequence && pk_and_sequence.first
658
+ end
659
+
660
+ def has_primary_key?(table_name, owner = nil, desc_table_name = nil) # :nodoc:
661
+ !pk_and_sequence_for(table_name, owner, desc_table_name).nil?
662
+ end
663
+
664
+ def primary_keys(table_name) # :nodoc:
665
+ (_owner, desc_table_name) = _connection.describe(table_name)
666
+
667
+ pks = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("table_name", desc_table_name)])
668
+ SELECT cc.column_name
669
+ FROM all_constraints c, all_cons_columns cc
670
+ WHERE c.owner = SYS_CONTEXT('userenv', 'current_schema')
671
+ AND c.table_name = :table_name
672
+ AND c.constraint_type = 'P'
673
+ AND cc.owner = c.owner
674
+ AND cc.constraint_name = c.constraint_name
675
+ order by cc.position
676
+ SQL
677
+ pks.map { |pk| oracle_downcase(pk) }
678
+ end
679
+
680
+ def columns_for_distinct(columns, orders) # :nodoc:
681
+ # construct a valid columns name for DISTINCT clause,
682
+ # ie. one that includes the ORDER BY columns, using FIRST_VALUE such that
683
+ # the inclusion of these columns doesn't invalidate the DISTINCT
684
+ #
685
+ # It does not construct DISTINCT clause. Just return column names for distinct.
686
+ order_columns = orders.reject(&:blank?).map { |s|
687
+ s = visitor.compile(s) unless s.is_a?(String)
688
+ # remove any ASC/DESC modifiers
689
+ s.gsub(/\s+(ASC|DESC)\s*?/i, "")
690
+ }.reject(&:blank?).map.with_index { |column, i|
691
+ "FIRST_VALUE(#{column}) OVER (PARTITION BY #{columns.join(', ')} ORDER BY #{column}) AS alias_#{i}__"
692
+ }
693
+ (order_columns << super).join(", ")
694
+ end
695
+
696
+ def temporary_table?(table_name) # :nodoc:
697
+ select_value_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("table_name", table_name.upcase)]) == "Y"
698
+ SELECT
699
+ temporary FROM all_tables WHERE table_name = :table_name and owner = SYS_CONTEXT('userenv', 'current_schema')
700
+ SQL
701
+ end
702
+
703
+ def max_identifier_length
704
+ supports_longer_identifier? ? 128 : 30
705
+ end
706
+ alias table_alias_length max_identifier_length
707
+ alias index_name_length max_identifier_length
708
+
709
+ # This is to ensure rails is not shortening the index name,
710
+ # in order to preserve the local shortening behavior.
711
+ def max_index_name_size
712
+ 128
713
+ end
714
+
715
+ def get_database_version
716
+ _connection.database_version
717
+ end
718
+
719
+ def check_version
720
+ version = get_database_version.join(".").to_f
721
+
722
+ if version < 10
723
+ raise "Your version of Oracle (#{version}) is too old. Active Record Oracle enhanced adapter supports Oracle >= 10g."
724
+ end
725
+ end
726
+
727
+ private def _connection
728
+ @unconfigured_connection || @raw_connection
729
+ end
730
+
731
+ class << self
732
+ def native_database_types
733
+ emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
734
+ end
735
+
736
+ def type_map
737
+ @type_map ||= Type::TypeMap.new.tap { |m| initialize_type_map(m) }
738
+ @type_map
739
+ end
740
+
741
+ def clear_type_map!
742
+ @type_map = nil
743
+ end
744
+
745
+ private
746
+ def initialize_type_map(m)
747
+ super
748
+ # oracle
749
+ register_class_with_precision m, %r(WITH TIME ZONE)i, Type::OracleEnhanced::TimestampTz
750
+ register_class_with_precision m, %r(WITH LOCAL TIME ZONE)i, Type::OracleEnhanced::TimestampLtz
751
+ register_class_with_limit m, %r(raw)i, Type::OracleEnhanced::Raw
752
+ register_class_with_limit m, %r{^(char)}i, Type::OracleEnhanced::CharacterString
753
+ register_class_with_limit m, %r{^(nchar)}i, Type::OracleEnhanced::String
754
+ register_class_with_limit m, %r(varchar)i, Type::OracleEnhanced::String
755
+ register_class_with_limit m, %r(clob)i, Type::OracleEnhanced::Text
756
+ register_class_with_limit m, %r(nclob)i, Type::OracleEnhanced::NationalCharacterText
757
+
758
+ m.register_type "NCHAR", Type::OracleEnhanced::NationalCharacterString.new
759
+ m.alias_type %r(NVARCHAR2)i, "NCHAR"
760
+
761
+ m.register_type(%r(NUMBER)i) do |sql_type|
762
+ scale = extract_scale(sql_type)
763
+ precision = extract_precision(sql_type)
764
+ limit = extract_limit(sql_type)
765
+ if scale == 0
766
+ Type::OracleEnhanced::Integer.new(precision: precision, limit: limit)
767
+ else
768
+ Type::Decimal.new(precision: precision, scale: scale)
769
+ end
770
+ end
771
+
772
+ if OracleEnhancedAdapter.emulate_booleans
773
+ m.register_type %r(^NUMBER\(1\))i, Type::Boolean.new
774
+ end
775
+ end
776
+ end
777
+
778
+ def type_map
779
+ self.class.type_map
780
+ end
781
+
782
+ def extract_value_from_default(default)
783
+ case default
784
+ when String
785
+ default.gsub("''", "'")
786
+ else
787
+ default
788
+ end
789
+ end
790
+
791
+ def extract_limit(sql_type) # :nodoc:
792
+ case sql_type
793
+ when /^bigint/i
794
+ 19
795
+ when /\((.*)\)/
796
+ $1.to_i
797
+ end
798
+ end
799
+
800
+ def translate_exception(exception, message:, sql:, binds:) # :nodoc:
801
+ case _connection.error_code(exception)
802
+ when 1
803
+ RecordNotUnique.new(message, sql: sql, binds: binds)
804
+ when 60
805
+ Deadlocked.new(message)
806
+ when 900, 904, 942, 955, 1418, 2289, 2449, 17008
807
+ ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
808
+ when 1400
809
+ ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
810
+ when 2291, 2292
811
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
812
+ when 12899
813
+ ValueTooLong.new(message, sql: sql, binds: binds)
814
+ else
815
+ super
816
+ end
817
+ end
818
+
819
+ # create bind object for type String
820
+ def bind_string(name, value)
821
+ ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new)
822
+ end
823
+
824
+ # call select_values using binds even if surrounding SQL preparation/execution is done + # with conn.unprepared_statement (like AR.to_sql)
825
+ def select_values_forcing_binds(arel, name, binds)
826
+ # remove possible force of unprepared SQL during dictionary access
827
+ unprepared_statement_forced = prepared_statements_disabled_cache.include?(object_id)
828
+ prepared_statements_disabled_cache.delete(object_id) if unprepared_statement_forced
829
+
830
+ select_values(arel, name, binds)
831
+ ensure
832
+ # Restore unprepared_statement setting for surrounding SQL
833
+ prepared_statements_disabled_cache.add(object_id) if unprepared_statement_forced
834
+ end
835
+
836
+ def select_value_forcing_binds(arel, name, binds)
837
+ single_value_from_rows(select_values_forcing_binds(arel, name, binds))
838
+ end
839
+
840
+ ActiveRecord::Type.register(:boolean, Type::OracleEnhanced::Boolean, adapter: :oracle_enhanced)
841
+ ActiveRecord::Type.register(:json, Type::OracleEnhanced::Json, adapter: :oracle_enhanced)
842
+ end
843
+ end
844
+ end
845
+
846
+ ## Register OracleEnhancedAdapter as the adapter to use for "oracle_enhanced" connection string
847
+ if ActiveRecord::ConnectionAdapters.respond_to?(:register)
848
+ ActiveRecord::ConnectionAdapters.register(
849
+ "oracle_enhanced",
850
+ "ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter",
851
+ "active_record/connection_adapters/oracle_enhanced_adapter"
852
+ )
853
+
854
+ # This is similar to the notion of emulating the original OracleAdapter but
855
+ # using the OracleEnhancedAdapter instead, but without using the emulate flag.
856
+ # Instead this will get picked up if you set the adapter to 'oracle' in the database config.
857
+ #
858
+ # Register OracleAdapter as the adapter to use for "oracle" connection string
859
+ ActiveRecord::ConnectionAdapters.register(
860
+ "oracle",
861
+ "ActiveRecord::ConnectionAdapters::OracleAdapter",
862
+ "active_record/connection_adapters/emulation/oracle_adapter"
863
+ )
864
+ end
865
+
866
+ require "active_record/connection_adapters/oracle_enhanced/version"
867
+
868
+ module ActiveRecord
869
+ autoload :OracleEnhancedProcedures, "active_record/connection_adapters/oracle_enhanced/procedures"
870
+ end
871
+
872
+ # Workaround for https://github.com/jruby/jruby/issues/6267
873
+ # Fixed in JRuby 9.3.0.0 or higher via https://github.com/jruby/jruby/pull/6683
874
+ if RUBY_ENGINE == "jruby" && !ObjectSpace::WeakMap.method_defined?(:values)
875
+ require "jruby"
876
+
877
+ class org.jruby::RubyObjectSpace::WeakMap
878
+ field_reader :map
879
+ end
880
+
881
+ class ObjectSpace::WeakMap
882
+ def values
883
+ JRuby.ref(self).map.values.reject(&:nil?)
884
+ end
885
+ end
886
+ end